diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2023-01-20 11:04:28 +0100 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2023-01-20 11:04:28 +0100 |
commit | a77fef4d29ed63c96c67692ceb6decef4f9cfbe4 (patch) | |
tree | cac5d4c36eab26b94d94b9268187800baa48286a | |
parent | b8e1b0251dc07b49eb643a4e3bfc391661dcae28 (diff) | |
parent | 944ed14b53454a7bd63891d487af2272eddc5e2f (diff) | |
download | barebox-a77fef4d29ed63c96c67692ceb6decef4f9cfbe4.tar.gz |
Merge branch 'for-next/jsmn'
-rw-r--r-- | include/bselftest.h | 3 | ||||
-rw-r--r-- | include/jsmn.h | 148 | ||||
-rw-r--r-- | lib/Kconfig | 5 | ||||
-rw-r--r-- | lib/Makefile | 1 | ||||
-rw-r--r-- | lib/jsmn.c | 457 | ||||
-rw-r--r-- | lib/vsprintf.c | 44 | ||||
-rw-r--r-- | test/self/Kconfig | 5 | ||||
-rw-r--r-- | test/self/Makefile | 1 | ||||
-rw-r--r-- | test/self/json.c | 146 | ||||
-rw-r--r-- | test/self/printf.c | 17 |
10 files changed, 826 insertions, 1 deletions
diff --git a/include/bselftest.h b/include/bselftest.h index f03c803b65..2bae6cd139 100644 --- a/include/bselftest.h +++ b/include/bselftest.h @@ -7,7 +7,8 @@ #include <init.h> enum bselftest_group { - BSELFTEST_core + BSELFTEST_core, + BSELFTEST_parser, }; struct selftest { diff --git a/include/jsmn.h b/include/jsmn.h new file mode 100644 index 0000000000..8f6db8d534 --- /dev/null +++ b/include/jsmn.h @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2010 Serge Zaitsev + */ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +#define JSMN_STRICT +#define JSMN_PARENT_LINKS + +#include <stddef.h> +#include <errno.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef JSMN_STATIC +#define JSMN_API static +#else +#define JSMN_API extern +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1 << 0, + JSMN_ARRAY = 1 << 1, + JSMN_STRING = 1 << 2, + JSMN_PRIMITIVE = 1 << 3 +} jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -ENOMEM, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -EINVAL, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -EMSGSIZE +}; + +/** + * JSON token description. + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string + */ +typedef struct jsmntok { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string. + */ +typedef struct jsmn_parser { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g. parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +JSMN_API void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each + * describing + * a single JSON object. + */ +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens); + +/** Returns `true` if `token` is a string and equal to `str`. */ +JSMN_API bool jsmn_str_eq(const char *str, const char *json, const jsmntok_t *token); + +/** Returns `true` if `token` is to `str`. */ +JSMN_API bool jsmn_eq(const char *val, const char *json, const jsmntok_t *token); + +/** Returns the token after the value at `tokens[0]`. */ +JSMN_API const jsmntok_t *jsmn_skip_value(const jsmntok_t *tokens); + +/** + * Returns a pointer to the value associated with `key` inside `json` starting + * at `tokens[0]`, which is expected to be an object, or returns `NULL` if `key` + * cannot be found. + */ +JSMN_API const jsmntok_t *jsmn_find_value(const char *key, const char *json, + const jsmntok_t *tokens); + +/** + * Locate the token at `path` inside `json` in a manner similar to JSONPath. + * + * Example: + * \code + * { + * "date": "...", + * "product": { + * "serial": "1234", + * } + * } + * \endcode + * + * To locate the serial number in the JSON object above, you would use the + * JSONPath expression "$.product.serial". The same thing can be accomplished + * with this call: + * + * \code + * const char *path[] = {"product", "serial", NULL}; + * const jsmntok_t *token = jsmn_locate(path, json, tokens); + * \endcode + * + * \remark This function cannot search inside arrays. + * + * @param path Null-terminated list of path elements. + * @param json JSON string to search in. + * @param tokens Tokens for `json`. + * + * @return Pointer to the value token or `NULL` if the token could not be found. + */ +JSMN_API const jsmntok_t *jsmn_locate(const char *path[], const char *json, + const jsmntok_t *tokens); + +/** + * Similar to `jsmn_locate` but returns a copy of the value or `NULL` if the + * value does not exist or is not a string. The caller takes ownership of the + * pointer returned. + */ +JSMN_API char *jsmn_strcpy(const char *path[], const char *json, const jsmntok_t *tokens); + +#ifdef __cplusplus +} +#endif + +#endif /* JSMN_H */ diff --git a/lib/Kconfig b/lib/Kconfig index 40a5fadd5c..b8bc9d63d4 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -8,6 +8,11 @@ config UNCOMPRESS bool select FILETYPE +config JSMN + bool "JSMN JSON Parser" if COMPILE_TEST + help + A minimalistic JSON parser. + config XXHASH bool diff --git a/lib/Makefile b/lib/Makefile index ba3cf6443f..3847862542 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -19,6 +19,7 @@ obj-y += readkey.o obj-y += kfifo.o obj-y += libbb.o obj-y += libgen.o +obj-$(CONFIG_JSMN) += jsmn.o obj-$(CONFIG_BLOBGEN) += blobgen.o obj-y += stringlist.o obj-y += cmdlinepart.o diff --git a/lib/jsmn.c b/lib/jsmn.c new file mode 100644 index 0000000000..7bdcc90f2f --- /dev/null +++ b/lib/jsmn.c @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (c) 2010 Serge Zaitsev + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <jsmn.h> + +/** + * Allocates a fresh unused token from the token pool. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, + const int start, const int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t': + case '\r': + case '\n': + case ' ': + case ',': + case ']': + case '}': + goto found; + default: + /* to quiet a warning from gcc*/ + break; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + /* Skip starting quote */ + parser->pos++; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': + case '/': + case '\\': + case 'b': + case 'f': + case 'r': + case 'n': + case 't': + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; + i++) { + /* If it isn't a hex character we have an error */ + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': + case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + return JSMN_ERROR_NOMEM; + } + if (parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; +#ifdef JSMN_STRICT + /* In strict mode an object or array can't become a key */ + if (t->type == JSMN_OBJECT) { + return JSMN_ERROR_INVAL; + } +#endif + t->size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': + case ']': + if (tokens == NULL) { + break; + } + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if (token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) { + return JSMN_ERROR_INVAL; + } + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + case '\t': + case '\r': + case '\n': + case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 't': + case 'f': + case 'n': + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + const jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +JSMN_API void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + +JSMN_API bool jsmn_eq(const char *val, const char *json, const jsmntok_t *token) +{ + size_t token_size = token->end - token->start; + return strlen(val) == token_size + && strncmp(json + token->start, val, token_size) == 0; +} + +JSMN_API bool jsmn_str_eq(const char *str, const char *json, const jsmntok_t *token) +{ + return token->type == JSMN_STRING && jsmn_eq(str, json, token); +} + +JSMN_API const jsmntok_t *jsmn_skip_value(const jsmntok_t *tokens) +{ + int max_index = tokens[0].end; + do { + ++tokens; + } while (tokens[0].start < max_index); + return &tokens[0]; +} + +JSMN_API const jsmntok_t *jsmn_find_value(const char *key, const char *json, + const jsmntok_t *tokens) +{ + int items; + if (tokens[0].type != JSMN_OBJECT || tokens[0].size == 0) + return NULL; + + items = tokens[0].size; + ++tokens; + + do { + if (jsmn_str_eq(key, json, tokens)) + return &tokens[1]; + tokens = --items ? jsmn_skip_value(&tokens[1]) : NULL; + } while (tokens); + + return NULL; +} + +JSMN_API const jsmntok_t *jsmn_locate(const char *path[], const char *json, + const jsmntok_t *tokens) +{ + int i = 0; + while (path[i] != NULL) { + const jsmntok_t *value = jsmn_find_value(path[i], json, tokens); + if (!value) + return NULL; + + switch (value->type) { + case JSMN_OBJECT: + case JSMN_ARRAY: + tokens = value; + ++i; + break; + case JSMN_UNDEFINED: + case JSMN_STRING: + case JSMN_PRIMITIVE: + return value; + } + } + + return tokens; +} + +JSMN_API char *jsmn_strcpy(const char *path[], const char *json, + const jsmntok_t *tokens) +{ + const jsmntok_t *node; + int value_size; + char *value; + + node = jsmn_locate(path, json, tokens); + if (!node || node->type != JSMN_STRING) + return NULL; + + value_size = node->end - node->start; + value = malloc(value_size + 1); + if (value) { + strncpy(value, json + node->start, value_size); + value[value_size] = '\0'; + } + + return value; +} diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 1291cf7dd9..7d943706dd 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -198,6 +198,27 @@ static char *string(char *buf, const char *end, const char *s, int field_width, return trailing_spaces(buf, end, len, &field_width, flags); } +static __maybe_unused char *string_array(char *buf, const char *end, char *const *s, + int field_width, int precision, int flags, + const char *separator) +{ + size_t i, len = strlen(separator); + + while (*s) { + buf = string(buf, end, *s, field_width, precision, flags); + if (!*++s) + break; + + for (i = 0; i < len; ++i) { + if (buf < end) + *buf = separator[i]; + ++buf; + } + } + + return buf; +} + static char *wstring(char *buf, const char *end, const wchar_t *s, int field_width, int precision, int flags) { @@ -370,6 +391,26 @@ char *hex_string(char *buf, const char *end, const u8 *addr, int field_width, } static noinline_for_stack +char *jsonpath_string(char *buf, const char *end, char *const *path, int field_width, + int precision, int flags, const char *fmt) +{ + if ((unsigned long)path < PAGE_SIZE) + return string(buf, end, "<NULL>", field_width, precision, flags); + + if (buf < end) + *buf = '$'; + ++buf; + + if (*path) { + if (buf < end) + *buf = '.'; + ++buf; + } + + return string_array(buf, end, path, field_width, precision, flags, "."); +} + +static noinline_for_stack char *address_val(char *buf, const char *end, const void *addr, int field_width, int precision, int flags, const char *fmt) { @@ -471,6 +512,9 @@ static char *pointer(const char *fmt, char *buf, const char *end, const void *pt if (IS_ENABLED(CONFIG_PRINTF_HEXSTR)) return hex_string(buf, end, ptr, field_width, precision, flags, fmt); break; + case 'J': + if (fmt[1] == 'P' && IS_ENABLED(CONFIG_JSMN)) + return jsonpath_string(buf, end, ptr, field_width, precision, flags, fmt); } return raw_pointer(buf, end, ptr, field_width, precision, flags); diff --git a/test/self/Kconfig b/test/self/Kconfig index 5c69819599..ce5048c70e 100644 --- a/test/self/Kconfig +++ b/test/self/Kconfig @@ -35,6 +35,7 @@ config SELFTEST_ENABLE_ALL select SELFTEST_ENVIRONMENT_VARIABLES if ENVIRONMENT_VARIABLES imply SELFTEST_FS_RAMFS imply SELFTEST_TFTP + imply SELFTEST_JSON help Selects all self-tests compatible with current configuration @@ -64,4 +65,8 @@ config SELFTEST_FS_RAMFS bool "ramfs selftest" depends on FS_RAMFS +config SELFTEST_JSON + bool "JSON selftest" + depends on JSMN + endif diff --git a/test/self/Makefile b/test/self/Makefile index c7c729cba0..4d2c0374c9 100644 --- a/test/self/Makefile +++ b/test/self/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_SELFTEST_PROGRESS_NOTIFIER) += progress-notifier.o obj-$(CONFIG_SELFTEST_OF_MANIPULATION) += of_manipulation.o of_manipulation.dtb.o obj-$(CONFIG_SELFTEST_ENVIRONMENT_VARIABLES) += envvar.o obj-$(CONFIG_SELFTEST_FS_RAMFS) += ramfs.o +obj-$(CONFIG_SELFTEST_JSON) += json.o diff --git a/test/self/json.c b/test/self/json.c new file mode 100644 index 0000000000..54323cf435 --- /dev/null +++ b/test/self/json.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <common.h> +#include <bselftest.h> +#include <jsmn.h> + +BSELFTEST_GLOBALS(); + +static const jsmntok_t* +__json_expect(const char *json, const jsmntok_t *token, const char **lookup, + jsmntype_t expected_type, const char *expected_value) +{ + bool success = true; + + total_tests++; + + if (token->type != expected_type) { + failed_tests++; + printf("%pJP: type mismatch: got %d, but %d expected\n", + lookup, token->type, expected_type); + success = false; + } + + if (!expected_value) + goto out; + + total_tests++; + + if (!jsmn_eq(expected_value, json, token)) { + failed_tests++; + printf("%pJP: string mismatch: got %.*s, but %s expected\n", + lookup, (int)(token->end - token->start), + &json[token->start], expected_value); + success = false; + } + +out: + return success ? token : NULL; +} + +static const jsmntok_t* +json_expect(const char *json, const jsmntok_t *tokens, const char **lookup, + jsmntype_t expected_type, const char *expected_value) +{ + const jsmntok_t *token; + + total_tests++; + + token = jsmn_locate(lookup, json, tokens); + if (!token) { + failed_tests++; + printf("%pJP: couldn't be located\n", lookup); + return NULL; + } + + return __json_expect(json, token, lookup, expected_type, expected_value); +} + +#define JP(...) (const char *[]) { __VA_ARGS__, NULL } + +/* Wonky indentation is intended */ +static const char json[] = +"{\n" +" \"null\" :null,\"number\" : 0x42,\n" +" \"object\": {\n" +" \"a\": \"b\",\n" +" \"C\": \"dE\"," +" \"e\": \"\"\n" +" }," +" \"array\": [ \"1\",\"2\",\t\t\"3\"],\n" +" \"boolean\": true,\n" +"\"string\": \"Hello World\n\"}\n"; + +static void test_json(void) +{ + ssize_t token_count; + const jsmntok_t *token; + jsmntok_t *tokens; + jsmn_parser parser; + char *string; + int ret; + + total_tests++; + + jsmn_init(&parser); + + /* Figure out how many tokens we need. */ + ret = jsmn_parse(&parser, json, sizeof(json), NULL, 0); + if (ret < 0) { + printf("failed to determine number of tokens: %d\n", ret); + failed_tests++; + return; + } + + token_count = ret; + + /* `token_count` is strictly less than `length` which is strictly less + * than CONFIG_SYS_EEPROM_SIZE (or 8K), so we should never overflow an + * int here. + */ + tokens = kmalloc_array(token_count, sizeof(jsmntok_t), GFP_KERNEL); + if (WARN_ON(!tokens)) + return; + + total_tests++; + + jsmn_init(&parser); + ret = jsmn_parse(&parser, json, sizeof(json), tokens, token_count); + if (ret < 0) { + printf("failed to parse JSON with tokens: %d\n", ret); + failed_tests++; + goto out; + } + + json_expect(json, tokens, JP("null"), JSMN_PRIMITIVE, "null"); + json_expect(json, tokens, JP("number"), JSMN_PRIMITIVE, "0x42"); + json_expect(json, tokens, JP("object"), JSMN_OBJECT, NULL); + json_expect(json, tokens, JP("object", "a"), JSMN_STRING, "b"); + json_expect(json, tokens, JP("object", "C"), JSMN_STRING, "dE"); + json_expect(json, tokens, JP("object", "e"), JSMN_STRING, ""); + + token = json_expect(json, tokens, JP("array"), JSMN_ARRAY, NULL); + + token = jsmn_skip_value(token); + __json_expect(json, token, JP("boolean"), JSMN_STRING, "boolean"); + + token = jsmn_skip_value(token); + __json_expect(json, token, JP("boolean"), JSMN_PRIMITIVE, "true"); + + string = jsmn_strcpy(JP("string"), json, tokens); + if (WARN_ON(!string)) + return; + + total_tests++; + if (strcmp(string, "Hello World\n")) { + failed_tests++; + printf("%pJP: string mismatch\n", JP("string")); + } + + free(string); +out: + free(tokens); +} +bselftest(parser, test_json); diff --git a/test/self/printf.c b/test/self/printf.c index 7a74660868..eae40ed242 100644 --- a/test/self/printf.c +++ b/test/self/printf.c @@ -305,6 +305,22 @@ test_pointer(void) errptr(); } +static void __init +test_jsonpath(void) +{ + if (!IS_ENABLED(CONFIG_JSMN)) { + pr_info("skipping jsonpath tests: CONFIG_JSMN disabled in config\n"); + skipped_tests += 5; + return; + } + + test("<NULL>", "%pJP", NULL); + test("$", "%pJP", &(char *[]){ NULL }); + test("$.1", "%pJP", &(char *[]){ "1", NULL }); + test("$.1.23", "%pJP", &(char *[]){ "1", "23", NULL }); + test("$.1.23.456", "%pJP", &(char *[]){ "1", "23", "456", NULL }); +} + static void __init test_printf(void) { alloced_buffer = malloc(BUF_SIZE + 2*PAD_SIZE); @@ -317,6 +333,7 @@ static void __init test_printf(void) test_string(); test_pointer(); test_hexstr(); + test_jsonpath(); free(alloced_buffer); } |