diff options
Diffstat (limited to 'contrib/qemu/qobject/json-parser.c')
| -rw-r--r-- | contrib/qemu/qobject/json-parser.c | 724 | 
1 files changed, 724 insertions, 0 deletions
diff --git a/contrib/qemu/qobject/json-parser.c b/contrib/qemu/qobject/json-parser.c new file mode 100644 index 000000000..e7947b340 --- /dev/null +++ b/contrib/qemu/qobject/json-parser.c @@ -0,0 +1,724 @@ +/* + * 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. + * + */ + +#include <stdarg.h> + +#include "qemu-common.h" +#include "qapi/qmp/qstring.h" +#include "qapi/qmp/qint.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qlist.h" +#include "qapi/qmp/qfloat.h" +#include "qapi/qmp/qbool.h" +#include "qapi/qmp/json-parser.h" +#include "qapi/qmp/json-lexer.h" +#include "qapi/qmp/qerror.h" + +typedef struct JSONParserContext +{ +    Error *err; +    struct { +        QObject **buf; +        size_t pos; +        size_t count; +    } tokens; +} JSONParserContext; + +#define BUG_ON(cond) assert(!(cond)) + +/** + * TODO + * + * 0) make errors meaningful again + * 1) add geometry information to tokens + * 3) should we return a parsed size? + * 4) deal with premature EOI + */ + +static QObject *parse_value(JSONParserContext *ctxt, va_list *ap); + +/** + * Token manipulators + * + * tokens are dictionaries that contain a type, a string value, and geometry information + * about a token identified by the lexer.  These are routines that make working with + * these objects a bit easier. + */ +static const char *token_get_value(QObject *obj) +{ +    return qdict_get_str(qobject_to_qdict(obj), "token"); +} + +static JSONTokenType token_get_type(QObject *obj) +{ +    return qdict_get_int(qobject_to_qdict(obj), "type"); +} + +static int token_is_operator(QObject *obj, char op) +{ +    const char *val; + +    if (token_get_type(obj) != JSON_OPERATOR) { +        return 0; +    } + +    val = token_get_value(obj); + +    return (val[0] == op) && (val[1] == 0); +} + +static int token_is_keyword(QObject *obj, const char *value) +{ +    if (token_get_type(obj) != JSON_KEYWORD) { +        return 0; +    } + +    return strcmp(token_get_value(obj), value) == 0; +} + +static int token_is_escape(QObject *obj, const char *value) +{ +    if (token_get_type(obj) != JSON_ESCAPE) { +        return 0; +    } + +    return (strcmp(token_get_value(obj), value) == 0); +} + +/** + * Error handler + */ +static void GCC_FMT_ATTR(3, 4) parse_error(JSONParserContext *ctxt, +                                           QObject *token, const char *msg, ...) +{ +    va_list ap; +    char message[1024]; +    va_start(ap, msg); +    vsnprintf(message, sizeof(message), msg, ap); +    va_end(ap); +    if (ctxt->err) { +        error_free(ctxt->err); +        ctxt->err = NULL; +    } +    error_set(&ctxt->err, QERR_JSON_PARSE_ERROR, message); +} + +/** + * String helpers + * + * These helpers are used to unescape strings. + */ +static void wchar_to_utf8(uint16_t wchar, char *buffer, size_t buffer_length) +{ +    if (wchar <= 0x007F) { +        BUG_ON(buffer_length < 2); + +        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; +    } +} + +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; +} + +/** + * parse_string(): Parse a json string and return a QObject + * + *  string + *      "" + *      " chars " + *  chars + *      char + *      char chars + *  char + *      any-Unicode-character- + *          except-"-or-\-or- + *          control-character + *      \" + *      \\ + *      \/ + *      \b + *      \f + *      \n + *      \r + *      \t + *      \u four-hex-digits  + */ +static QString *qstring_from_escaped_str(JSONParserContext *ctxt, QObject *token) +{ +    const char *ptr = token_get_value(token); +    QString *str; +    int double_quote = 1; + +    if (*ptr == '"') { +        double_quote = 1; +    } else { +        double_quote = 0; +    } +    ptr++; + +    str = qstring_new(); +    while (*ptr &&  +           ((double_quote && *ptr != '"') || (!double_quote && *ptr != '\''))) { +        if (*ptr == '\\') { +            ptr++; + +            switch (*ptr) { +            case '"': +                qstring_append(str, "\""); +                ptr++; +                break; +            case '\'': +                qstring_append(str, "'"); +                ptr++; +                break; +            case '\\': +                qstring_append(str, "\\"); +                ptr++; +                break; +            case '/': +                qstring_append(str, "/"); +                ptr++; +                break; +            case 'b': +                qstring_append(str, "\b"); +                ptr++; +                break; +            case 'f': +                qstring_append(str, "\f"); +                ptr++; +                break; +            case 'n': +                qstring_append(str, "\n"); +                ptr++; +                break; +            case 'r': +                qstring_append(str, "\r"); +                ptr++; +                break; +            case 't': +                qstring_append(str, "\t"); +                ptr++; +                break; +            case 'u': { +                uint16_t unicode_char = 0; +                char utf8_char[4]; +                int i = 0; + +                ptr++; + +                for (i = 0; i < 4; i++) { +                    if (qemu_isxdigit(*ptr)) { +                        unicode_char |= hex2decimal(*ptr) << ((3 - i) * 4); +                    } else { +                        parse_error(ctxt, token, +                                    "invalid hex escape sequence in string"); +                        goto out; +                    } +                    ptr++; +                } + +                wchar_to_utf8(unicode_char, utf8_char, sizeof(utf8_char)); +                qstring_append(str, utf8_char); +            }   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); +        } +    } + +    return str; + +out: +    QDECREF(str); +    return NULL; +} + +static QObject *parser_context_pop_token(JSONParserContext *ctxt) +{ +    QObject *token; +    g_assert(ctxt->tokens.pos < ctxt->tokens.count); +    token = ctxt->tokens.buf[ctxt->tokens.pos]; +    ctxt->tokens.pos++; +    return token; +} + +/* Note: parser_context_{peek|pop}_token do not increment the + * token object's refcount. In both cases the references will continue + * to be tracked and cleaned up in parser_context_free(), so do not + * attempt to free the token object. + */ +static QObject *parser_context_peek_token(JSONParserContext *ctxt) +{ +    QObject *token; +    g_assert(ctxt->tokens.pos < ctxt->tokens.count); +    token = ctxt->tokens.buf[ctxt->tokens.pos]; +    return token; +} + +static JSONParserContext parser_context_save(JSONParserContext *ctxt) +{ +    JSONParserContext saved_ctxt = {0}; +    saved_ctxt.tokens.pos = ctxt->tokens.pos; +    saved_ctxt.tokens.count = ctxt->tokens.count; +    saved_ctxt.tokens.buf = ctxt->tokens.buf; +    return saved_ctxt; +} + +static void parser_context_restore(JSONParserContext *ctxt, +                                   JSONParserContext saved_ctxt) +{ +    ctxt->tokens.pos = saved_ctxt.tokens.pos; +    ctxt->tokens.count = saved_ctxt.tokens.count; +    ctxt->tokens.buf = saved_ctxt.tokens.buf; +} + +static void tokens_append_from_iter(QObject *obj, void *opaque) +{ +    JSONParserContext *ctxt = opaque; +    g_assert(ctxt->tokens.pos < ctxt->tokens.count); +    ctxt->tokens.buf[ctxt->tokens.pos++] = obj; +    qobject_incref(obj); +} + +static JSONParserContext *parser_context_new(QList *tokens) +{ +    JSONParserContext *ctxt; +    size_t count; + +    if (!tokens) { +        return NULL; +    } + +    count = qlist_size(tokens); +    if (count == 0) { +        return NULL; +    } + +    ctxt = g_malloc0(sizeof(JSONParserContext)); +    ctxt->tokens.pos = 0; +    ctxt->tokens.count = count; +    ctxt->tokens.buf = g_malloc(count * sizeof(QObject *)); +    qlist_iter(tokens, tokens_append_from_iter, ctxt); +    ctxt->tokens.pos = 0; + +    return ctxt; +} + +/* to support error propagation, ctxt->err must be freed separately */ +static void parser_context_free(JSONParserContext *ctxt) +{ +    int i; +    if (ctxt) { +        for (i = 0; i < ctxt->tokens.count; i++) { +            qobject_decref(ctxt->tokens.buf[i]); +        } +        g_free(ctxt->tokens.buf); +        g_free(ctxt); +    } +} + +/** + * Parsing rules + */ +static int parse_pair(JSONParserContext *ctxt, QDict *dict, va_list *ap) +{ +    QObject *key = NULL, *token = NULL, *value, *peek; +    JSONParserContext saved_ctxt = parser_context_save(ctxt); + +    peek = parser_context_peek_token(ctxt); +    if (peek == NULL) { +        parse_error(ctxt, NULL, "premature EOI"); +        goto out; +    } + +    key = parse_value(ctxt, ap); +    if (!key || qobject_type(key) != QTYPE_QSTRING) { +        parse_error(ctxt, peek, "key is not a string in object"); +        goto out; +    } + +    token = parser_context_pop_token(ctxt); +    if (token == NULL) { +        parse_error(ctxt, NULL, "premature EOI"); +        goto out; +    } + +    if (!token_is_operator(token, ':')) { +        parse_error(ctxt, token, "missing : in object pair"); +        goto out; +    } + +    value = parse_value(ctxt, ap); +    if (value == NULL) { +        parse_error(ctxt, token, "Missing value in dict"); +        goto out; +    } + +    qdict_put_obj(dict, qstring_get_str(qobject_to_qstring(key)), value); + +    qobject_decref(key); + +    return 0; + +out: +    parser_context_restore(ctxt, saved_ctxt); +    qobject_decref(key); + +    return -1; +} + +static QObject *parse_object(JSONParserContext *ctxt, va_list *ap) +{ +    QDict *dict = NULL; +    QObject *token, *peek; +    JSONParserContext saved_ctxt = parser_context_save(ctxt); + +    token = parser_context_pop_token(ctxt); +    if (token == NULL) { +        goto out; +    } + +    if (!token_is_operator(token, '{')) { +        goto out; +    } +    token = NULL; + +    dict = qdict_new(); + +    peek = parser_context_peek_token(ctxt); +    if (peek == NULL) { +        parse_error(ctxt, NULL, "premature EOI"); +        goto out; +    } + +    if (!token_is_operator(peek, '}')) { +        if (parse_pair(ctxt, dict, ap) == -1) { +            goto out; +        } + +        token = parser_context_pop_token(ctxt); +        if (token == NULL) { +            parse_error(ctxt, NULL, "premature EOI"); +            goto out; +        } + +        while (!token_is_operator(token, '}')) { +            if (!token_is_operator(token, ',')) { +                parse_error(ctxt, token, "expected separator in dict"); +                goto out; +            } +            token = NULL; + +            if (parse_pair(ctxt, dict, ap) == -1) { +                goto out; +            } + +            token = parser_context_pop_token(ctxt); +            if (token == NULL) { +                parse_error(ctxt, NULL, "premature EOI"); +                goto out; +            } +        } +        token = NULL; +    } else { +        token = parser_context_pop_token(ctxt); +        token = NULL; +    } + +    return QOBJECT(dict); + +out: +    parser_context_restore(ctxt, saved_ctxt); +    QDECREF(dict); +    return NULL; +} + +static QObject *parse_array(JSONParserContext *ctxt, va_list *ap) +{ +    QList *list = NULL; +    QObject *token, *peek; +    JSONParserContext saved_ctxt = parser_context_save(ctxt); + +    token = parser_context_pop_token(ctxt); +    if (token == NULL) { +        goto out; +    } + +    if (!token_is_operator(token, '[')) { +        token = NULL; +        goto out; +    } +    token = NULL; + +    list = qlist_new(); + +    peek = parser_context_peek_token(ctxt); +    if (peek == NULL) { +        parse_error(ctxt, NULL, "premature EOI"); +        goto out; +    } + +    if (!token_is_operator(peek, ']')) { +        QObject *obj; + +        obj = parse_value(ctxt, ap); +        if (obj == NULL) { +            parse_error(ctxt, token, "expecting value"); +            goto out; +        } + +        qlist_append_obj(list, obj); + +        token = parser_context_pop_token(ctxt); +        if (token == NULL) { +            parse_error(ctxt, NULL, "premature EOI"); +            goto out; +        } + +        while (!token_is_operator(token, ']')) { +            if (!token_is_operator(token, ',')) { +                parse_error(ctxt, token, "expected separator in list"); +                goto out; +            } + +            token = NULL; + +            obj = parse_value(ctxt, ap); +            if (obj == NULL) { +                parse_error(ctxt, token, "expecting value"); +                goto out; +            } + +            qlist_append_obj(list, obj); + +            token = parser_context_pop_token(ctxt); +            if (token == NULL) { +                parse_error(ctxt, NULL, "premature EOI"); +                goto out; +            } +        } + +        token = NULL; +    } else { +        token = parser_context_pop_token(ctxt); +        token = NULL; +    } + +    return QOBJECT(list); + +out: +    parser_context_restore(ctxt, saved_ctxt); +    QDECREF(list); +    return NULL; +} + +static QObject *parse_keyword(JSONParserContext *ctxt) +{ +    QObject *token, *ret; +    JSONParserContext saved_ctxt = parser_context_save(ctxt); + +    token = parser_context_pop_token(ctxt); +    if (token == NULL) { +        goto out; +    } + +    if (token_get_type(token) != JSON_KEYWORD) { +        goto out; +    } + +    if (token_is_keyword(token, "true")) { +        ret = QOBJECT(qbool_from_int(true)); +    } else if (token_is_keyword(token, "false")) { +        ret = QOBJECT(qbool_from_int(false)); +    } else { +        parse_error(ctxt, token, "invalid keyword `%s'", token_get_value(token)); +        goto out; +    } + +    return ret; + +out:  +    parser_context_restore(ctxt, saved_ctxt); + +    return NULL; +} + +static QObject *parse_escape(JSONParserContext *ctxt, va_list *ap) +{ +    QObject *token = NULL, *obj; +    JSONParserContext saved_ctxt = parser_context_save(ctxt); + +    if (ap == NULL) { +        goto out; +    } + +    token = parser_context_pop_token(ctxt); +    if (token == NULL) { +        goto out; +    } + +    if (token_is_escape(token, "%p")) { +        obj = va_arg(*ap, QObject *); +    } else if (token_is_escape(token, "%i")) { +        obj = QOBJECT(qbool_from_int(va_arg(*ap, int))); +    } else if (token_is_escape(token, "%d")) { +        obj = QOBJECT(qint_from_int(va_arg(*ap, int))); +    } else if (token_is_escape(token, "%ld")) { +        obj = QOBJECT(qint_from_int(va_arg(*ap, long))); +    } else if (token_is_escape(token, "%lld") || +               token_is_escape(token, "%I64d")) { +        obj = QOBJECT(qint_from_int(va_arg(*ap, long long))); +    } else if (token_is_escape(token, "%s")) { +        obj = QOBJECT(qstring_from_str(va_arg(*ap, const char *))); +    } else if (token_is_escape(token, "%f")) { +        obj = QOBJECT(qfloat_from_double(va_arg(*ap, double))); +    } else { +        goto out; +    } + +    return obj; + +out: +    parser_context_restore(ctxt, saved_ctxt); + +    return NULL; +} + +static QObject *parse_literal(JSONParserContext *ctxt) +{ +    QObject *token, *obj; +    JSONParserContext saved_ctxt = parser_context_save(ctxt); + +    token = parser_context_pop_token(ctxt); +    if (token == NULL) { +        goto out; +    } + +    switch (token_get_type(token)) { +    case JSON_STRING: +        obj = QOBJECT(qstring_from_escaped_str(ctxt, token)); +        break; +    case JSON_INTEGER: { +        /* A possibility exists that this is a whole-valued float where the +         * fractional part was left out due to being 0 (.0). It's not a big +         * deal to treat these as ints in the parser, so long as users of the +         * resulting QObject know to expect a QInt in place of a QFloat in +         * cases like these. +         * +         * However, in some cases these values will overflow/underflow a +         * QInt/int64 container, thus we should assume these are to be handled +         * as QFloats/doubles rather than silently changing their values. +         * +         * strtoll() indicates these instances by setting errno to ERANGE +         */ +        int64_t value; + +        errno = 0; /* strtoll doesn't set errno on success */ +        value = strtoll(token_get_value(token), NULL, 10); +        if (errno != ERANGE) { +            obj = QOBJECT(qint_from_int(value)); +            break; +        } +        /* fall through to JSON_FLOAT */ +    } +    case JSON_FLOAT: +        /* FIXME dependent on locale */ +        obj = QOBJECT(qfloat_from_double(strtod(token_get_value(token), NULL))); +        break; +    default: +        goto out; +    } + +    return obj; + +out: +    parser_context_restore(ctxt, saved_ctxt); + +    return NULL; +} + +static QObject *parse_value(JSONParserContext *ctxt, va_list *ap) +{ +    QObject *obj; + +    obj = parse_object(ctxt, ap); +    if (obj == NULL) { +        obj = parse_array(ctxt, ap); +    } +    if (obj == NULL) { +        obj = parse_escape(ctxt, ap); +    } +    if (obj == NULL) { +        obj = parse_keyword(ctxt); +    }  +    if (obj == NULL) { +        obj = parse_literal(ctxt); +    } + +    return obj; +} + +QObject *json_parser_parse(QList *tokens, va_list *ap) +{ +    return json_parser_parse_err(tokens, ap, NULL); +} + +QObject *json_parser_parse_err(QList *tokens, va_list *ap, Error **errp) +{ +    JSONParserContext *ctxt = parser_context_new(tokens); +    QObject *result; + +    if (!ctxt) { +        return NULL; +    } + +    result = parse_value(ctxt, ap); + +    error_propagate(errp, ctxt->err); + +    parser_context_free(ctxt); + +    return result; +}  | 
