Merge pull request #10316 from poettering/json-api

just the json stuff from #9762
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2018-10-10 14:21:18 +02:00 committed by GitHub
commit 2a56a88f46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 4078 additions and 1 deletions

57
src/basic/json-internal.h Normal file
View File

@ -0,0 +1,57 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "json.h"
/* This header should include all prototypes only the JSON parser itself and
* its tests need access to. Normal code consuming the JSON parser should not
* interface with this. */
typedef union JsonValue {
/* Encodes a simple value. On x86-64 this structure is 16 bytes wide (as long double is 128bit). */
bool boolean;
long double real;
intmax_t integer;
uintmax_t unsig;
} JsonValue;
/* Let's protect us against accidental structure size changes on our most relevant arch */
#ifdef __x86_64__
assert_cc(sizeof(JsonValue) == 16U);
#endif
#define JSON_VALUE_NULL ((JsonValue) {})
/* We use fake JsonVariant objects for some special values, in order to avoid memory allocations for them. Note that
* effectively this means that there are multiple ways to encode the same objects: via these magic values or as
* properly allocated JsonVariant. We convert between both on-the-fly as necessary. */
#define JSON_VARIANT_MAGIC_TRUE ((JsonVariant*) 1)
#define JSON_VARIANT_MAGIC_FALSE ((JsonVariant*) 2)
#define JSON_VARIANT_MAGIC_NULL ((JsonVariant*) 3)
#define JSON_VARIANT_MAGIC_ZERO_INTEGER ((JsonVariant*) 4)
#define JSON_VARIANT_MAGIC_ZERO_UNSIGNED ((JsonVariant*) 5)
#define JSON_VARIANT_MAGIC_ZERO_REAL ((JsonVariant*) 6)
#define JSON_VARIANT_MAGIC_EMPTY_STRING ((JsonVariant*) 7)
#define JSON_VARIANT_MAGIC_EMPTY_ARRAY ((JsonVariant*) 8)
#define JSON_VARIANT_MAGIC_EMPTY_OBJECT ((JsonVariant*) 9)
enum { /* JSON tokens */
JSON_TOKEN_END,
JSON_TOKEN_COLON,
JSON_TOKEN_COMMA,
JSON_TOKEN_OBJECT_OPEN,
JSON_TOKEN_OBJECT_CLOSE,
JSON_TOKEN_ARRAY_OPEN,
JSON_TOKEN_ARRAY_CLOSE,
JSON_TOKEN_STRING,
JSON_TOKEN_REAL,
JSON_TOKEN_INTEGER,
JSON_TOKEN_UNSIGNED,
JSON_TOKEN_BOOLEAN,
JSON_TOKEN_NULL,
_JSON_TOKEN_MAX,
_JSON_TOKEN_INVALID = -1,
};
int json_tokenize(const char **p, char **ret_string, JsonValue *ret_value, unsigned *ret_line, unsigned *ret_column, void **state, unsigned *line, unsigned *column);

3290
src/basic/json.c Normal file

File diff suppressed because it is too large Load Diff

274
src/basic/json.h Normal file
View File

@ -0,0 +1,274 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "macro.h"
#include "string-util.h"
#include "util.h"
/*
In case you wonder why we have our own JSON implementation, here are a couple of reasons why this implementation has
benefits over various other implementatins:
- We need support for 64bit signed and unsigned integers, i.e. the full 64,5bit range of -922337203685477580818446744073709551615
- All our variants are immutable after creation
- Special values such as true, false, zero, null, empty strings, empty array, empty objects require zero dynamic memory
- Progressive parsing
- Our integer/real type implicitly converts, but only if that's safe and loss-lessly possible
- There's a "builder" for putting together objects easily in varargs function calls
- There's a "dispatcher" for mapping objects to C data structures
- Every variant optionally carries parsing location information, which simplifies debugging and parse log error generation
- Formatter has color, line, column support
Limitations:
- Doesn't allow embedded NUL in strings
- Can't store integers outside of the -922337203685477580818446744073709551615 range (it will use 'long double' for
values outside this range, which is lossy)
- Can't store negative zero (will be treated identical to positive zero, and not retained across serialization)
- Can't store non-integer numbers that can't be stored in "long double" losslessly
- Allows creation and parsing of objects with duplicate keys. The "dispatcher" will refuse them however. This means
we can parse and pass around such objects, but will carefully refuse them when we convert them into our own data.
(These limitations should be pretty much in line with those of other JSON implementations, in fact might be less
limiting in most cases even.)
*/
typedef struct JsonVariant JsonVariant;
typedef enum JsonVariantType {
JSON_VARIANT_STRING,
JSON_VARIANT_INTEGER,
JSON_VARIANT_UNSIGNED,
JSON_VARIANT_REAL,
JSON_VARIANT_NUMBER, /* This a pseudo-type: we can never create variants of this type, but we use it as wildcard check for the above three types */
JSON_VARIANT_BOOLEAN,
JSON_VARIANT_ARRAY,
JSON_VARIANT_OBJECT,
JSON_VARIANT_NULL,
_JSON_VARIANT_TYPE_MAX,
_JSON_VARIANT_TYPE_INVALID = -1
} JsonVariantType;
int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n);
int json_variant_new_integer(JsonVariant **ret, intmax_t i);
int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u);
int json_variant_new_real(JsonVariant **ret, long double d);
int json_variant_new_boolean(JsonVariant **ret, bool b);
int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n);
int json_variant_new_array_bytes(JsonVariant **ret, const void *p, size_t n);
int json_variant_new_array_strv(JsonVariant **ret, char **l);
int json_variant_new_object(JsonVariant **ret, JsonVariant **array, size_t n);
int json_variant_new_null(JsonVariant **ret);
static inline int json_variant_new_string(JsonVariant **ret, const char *s) {
return json_variant_new_stringn(ret, s, strlen_ptr(s));
}
JsonVariant *json_variant_ref(JsonVariant *v);
JsonVariant *json_variant_unref(JsonVariant *v);
void json_variant_unref_many(JsonVariant **array, size_t n);
DEFINE_TRIVIAL_CLEANUP_FUNC(JsonVariant *, json_variant_unref);
const char *json_variant_string(JsonVariant *v);
intmax_t json_variant_integer(JsonVariant *v);
uintmax_t json_variant_unsigned(JsonVariant *v);
long double json_variant_real(JsonVariant *v);
bool json_variant_boolean(JsonVariant *v);
JsonVariantType json_variant_type(JsonVariant *v);
bool json_variant_has_type(JsonVariant *v, JsonVariantType type);
static inline bool json_variant_is_string(JsonVariant *v) {
return json_variant_has_type(v, JSON_VARIANT_STRING);
}
static inline bool json_variant_is_integer(JsonVariant *v) {
return json_variant_has_type(v, JSON_VARIANT_INTEGER);
}
static inline bool json_variant_is_unsigned(JsonVariant *v) {
return json_variant_has_type(v, JSON_VARIANT_UNSIGNED);
}
static inline bool json_variant_is_real(JsonVariant *v) {
return json_variant_has_type(v, JSON_VARIANT_REAL);
}
static inline bool json_variant_is_number(JsonVariant *v) {
return json_variant_has_type(v, JSON_VARIANT_NUMBER);
}
static inline bool json_variant_is_boolean(JsonVariant *v) {
return json_variant_has_type(v, JSON_VARIANT_BOOLEAN);
}
static inline bool json_variant_is_array(JsonVariant *v) {
return json_variant_has_type(v, JSON_VARIANT_ARRAY);
}
static inline bool json_variant_is_object(JsonVariant *v) {
return json_variant_has_type(v, JSON_VARIANT_OBJECT);
}
static inline bool json_variant_is_null(JsonVariant *v) {
return json_variant_has_type(v, JSON_VARIANT_NULL);
}
bool json_variant_is_negative(JsonVariant *v);
size_t json_variant_elements(JsonVariant *v);
JsonVariant *json_variant_by_index(JsonVariant *v, size_t index);
JsonVariant *json_variant_by_key(JsonVariant *v, const char *key);
JsonVariant *json_variant_by_key_full(JsonVariant *v, const char *key, JsonVariant **ret_key);
bool json_variant_equal(JsonVariant *a, JsonVariant *b);
struct json_variant_foreach_state {
JsonVariant *variant;
size_t idx;
};
#define JSON_VARIANT_ARRAY_FOREACH(i, v) \
for (struct json_variant_foreach_state _state = { (v), 0 }; \
_state.idx < json_variant_elements(_state.variant) && \
({ i = json_variant_by_index(_state.variant, _state.idx); \
true; }); \
_state.idx++)
#define JSON_VARIANT_OBJECT_FOREACH(k, e, v) \
for (struct json_variant_foreach_state _state = { (v), 0 }; \
_state.idx < json_variant_elements(_state.variant) && \
({ k = json_variant_by_index(_state.variant, _state.idx); \
e = json_variant_by_index(_state.variant, _state.idx + 1); \
true; }); \
_state.idx += 2)
int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column);
enum {
JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */
JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */
JSON_FORMAT_COLOR = 1 << 2, /* insert ANSI color sequences */
JSON_FORMAT_SOURCE = 1 << 3, /* prefix with source filename/line/column */
JSON_FORMAT_SSE = 1 << 4, /* prefix/suffix with W3C server-sent events */
JSON_FORMAT_SEQ = 1 << 5, /* prefix/suffix with RFC 7464 application/json-seq */
};
int json_variant_format(JsonVariant *v, unsigned flags, char **ret);
void json_variant_dump(JsonVariant *v, unsigned flags, FILE *f, const char *prefix);
int json_parse(const char *string, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
int json_parse_continue(const char **p, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
int json_parse_file(FILE *f, const char *path, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
enum {
_JSON_BUILD_STRING,
_JSON_BUILD_INTEGER,
_JSON_BUILD_UNSIGNED,
_JSON_BUILD_REAL,
_JSON_BUILD_BOOLEAN,
_JSON_BUILD_ARRAY_BEGIN,
_JSON_BUILD_ARRAY_END,
_JSON_BUILD_OBJECT_BEGIN,
_JSON_BUILD_OBJECT_END,
_JSON_BUILD_PAIR,
_JSON_BUILD_NULL,
_JSON_BUILD_VARIANT,
_JSON_BUILD_LITERAL,
_JSON_BUILD_STRV,
_JSON_BUILD_MAX,
};
#define JSON_BUILD_STRING(s) _JSON_BUILD_STRING, ({ const char *_x = s; _x; })
#define JSON_BUILD_INTEGER(i) _JSON_BUILD_INTEGER, ({ intmax_t _x = i; _x; })
#define JSON_BUILD_UNSIGNED(u) _JSON_BUILD_UNSIGNED, ({ uintmax_t _x = u; _x; })
#define JSON_BUILD_REAL(d) _JSON_BUILD_REAL, ({ long double _x = d; _x; })
#define JSON_BUILD_BOOLEAN(b) _JSON_BUILD_BOOLEAN, ({ bool _x = b; _x; })
#define JSON_BUILD_ARRAY(...) _JSON_BUILD_ARRAY_BEGIN, __VA_ARGS__, _JSON_BUILD_ARRAY_END
#define JSON_BUILD_OBJECT(...) _JSON_BUILD_OBJECT_BEGIN, __VA_ARGS__, _JSON_BUILD_OBJECT_END
#define JSON_BUILD_PAIR(n, ...) _JSON_BUILD_PAIR, ({ const char *_x = n; _x; }), __VA_ARGS__
#define JSON_BUILD_NULL _JSON_BUILD_NULL
#define JSON_BUILD_VARIANT(v) _JSON_BUILD_VARIANT, ({ JsonVariant *_x = v; _x; })
#define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, ({ const char *_x = l; _x; })
#define JSON_BUILD_STRV(l) _JSON_BUILD_STRV, ({ char **_x = l; _x; })
int json_build(JsonVariant **ret, ...);
int json_buildv(JsonVariant **ret, va_list ap);
/* A bitmask of flags used by the dispatch logic. Note that this is a combined bit mask, that is generated from the bit
* mask originally passed into json_dispatch(), the individual bitmask associated with the static JsonDispatch callout
* entry, as well the bitmask specified for json_log() calls */
typedef enum JsonDispatchFlags {
/* The following three may be set in JsonDispatch's .flags field or the json_dispatch() flags parameter */
JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this property? */
JSON_MANDATORY = 1 << 1, /* Should existance of this property be mandatory? */
JSON_LOG = 1 << 2, /* Should the parser log about errors? */
/* The following two may be passed into log_json() in addition to the three above */
JSON_DEBUG = 1 << 3, /* Indicates that this log message is a debug message */
JSON_WARNING = 1 << 4, /* Indicates that this log message is a warning message */
} JsonDispatchFlags;
typedef int (*JsonDispatchCallback)(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
typedef struct JsonDispatch {
const char *name;
JsonVariantType type;
JsonDispatchCallback callback;
size_t offset;
JsonDispatchFlags flags;
} JsonDispatch;
int json_dispatch(JsonVariant *v, const JsonDispatch table[], JsonDispatchCallback bad, JsonDispatchFlags flags, void *userdata);
int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_strv(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_boolean(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_tristate(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_variant(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_integer(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_unsigned(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
assert_cc(sizeof(uintmax_t) == sizeof(uint64_t))
#define json_dispatch_uint64 json_dispatch_unsigned
assert_cc(sizeof(intmax_t) == sizeof(int64_t))
#define json_dispatch_int64 json_dispatch_integer
static inline int json_dispatch_level(JsonDispatchFlags flags) {
/* Did the user request no logging? If so, then never log higher than LOG_DEBUG. Also, if this is marked as
* debug message, then also log at debug level. */
if (!(flags & JSON_LOG) ||
(flags & JSON_DEBUG))
return LOG_DEBUG;
/* Are we invoked in permissive mode, or is this explicitly marked as warning message? Then this should be
* printed at LOG_WARNING */
if (flags & (JSON_PERMISSIVE|JSON_WARNING))
return LOG_WARNING;
/* Otherwise it's an error. */
return LOG_ERR;
}
int json_log_internal(JsonVariant *variant, int level, int error, const char *file, int line, const char *func, const char *format, ...) _printf_(7, 8);
#define json_log(variant, flags, error, ...) \
({ \
int _level = json_dispatch_level(flags), _e = (error); \
(log_get_max_level() >= LOG_PRI(_level)) \
? json_log_internal(variant, _level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \
: -abs(_e); \
})
const char *json_variant_type_to_string(JsonVariantType t);
JsonVariantType json_variant_type_from_string(const char *s);

View File

@ -99,6 +99,9 @@ basic_sources = files('''
ioprio.h
journal-importer.c
journal-importer.h
json-internal.h
json.c
json.h
khash.c
khash.h
label.c
@ -313,7 +316,8 @@ libbasic = static_library(
libcap,
libblkid,
libmount,
libselinux],
libselinux,
libm],
c_args : ['-fvisibility=default'],
install : false)

30
src/fuzz/fuzz-json.c Normal file
View File

@ -0,0 +1,30 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "fd-util.h"
#include "fuzz.h"
#include "json.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
_cleanup_free_ char *out = NULL; /* out should be freed after g */
size_t out_size;
_cleanup_fclose_ FILE *f = NULL, *g = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
if (size == 0)
return 0;
f = fmemopen((char*) data, size, "re");
assert_se(f);
if (json_parse_file(f, NULL, &v, NULL, NULL) < 0)
return 0;
g = open_memstream(&out, &out_size);
assert_se(g);
json_variant_dump(v, 0, g, NULL);
json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, g, NULL);
return 0;
}

View File

@ -37,6 +37,10 @@ fuzzers += [
libsystemd_network],
[]],
[['src/fuzz/fuzz-json.c'],
[libshared],
[]],
[['src/fuzz/fuzz-unit-file.c'],
[libcore,
libshared],

View File

@ -191,6 +191,10 @@ tests += [
[],
[]],
[['src/test/test-json.c'],
[],
[]],
[['src/test/test-mount-util.c'],
[],
[]],

411
src/test/test-json.c Normal file
View File

@ -0,0 +1,411 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <math.h>
#if HAVE_VALGRIND_VALGRIND_H
#include <valgrind/valgrind.h>
#endif
#include "alloc-util.h"
#include "fd-util.h"
#include "json-internal.h"
#include "json.h"
#include "string-util.h"
#include "strv.h"
#include "util.h"
static void test_tokenizer(const char *data, ...) {
unsigned line = 0, column = 0;
void *state = NULL;
va_list ap;
va_start(ap, data);
for (;;) {
unsigned token_line, token_column;
_cleanup_free_ char *str = NULL;
JsonValue v = JSON_VALUE_NULL;
int t, tt;
t = json_tokenize(&data, &str, &v, &token_line, &token_column, &state, &line, &column);
tt = va_arg(ap, int);
assert_se(t == tt);
if (t == JSON_TOKEN_END || t < 0)
break;
else if (t == JSON_TOKEN_STRING) {
const char *nn;
nn = va_arg(ap, const char *);
assert_se(streq_ptr(nn, str));
} else if (t == JSON_TOKEN_REAL) {
long double d;
d = va_arg(ap, long double);
#if HAVE_VALGRIND_VALGRIND_H
if (!RUNNING_ON_VALGRIND)
#endif
/* Valgrind doesn't support long double calculations and automatically downgrades to 80bit:
* http://www.valgrind.org/docs/manual/manual-core.html#manual-core.limits */
assert_se(fabsl(d - v.real) < 0.001L);
} else if (t == JSON_TOKEN_INTEGER) {
intmax_t i;
i = va_arg(ap, intmax_t);
assert_se(i == v.integer);
} else if (t == JSON_TOKEN_UNSIGNED) {
uintmax_t u;
u = va_arg(ap, uintmax_t);
assert_se(u == v.unsig);
} else if (t == JSON_TOKEN_BOOLEAN) {
bool b;
b = va_arg(ap, int);
assert_se(b == v.boolean);
}
}
va_end(ap);
}
typedef void (*Test)(JsonVariant *);
static void test_variant(const char *data, Test test) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
_cleanup_free_ char *s = NULL;
int r;
r = json_parse(data, &v, NULL, NULL);
assert_se(r == 0);
assert_se(v);
r = json_variant_format(v, 0, &s);
assert_se(r >= 0);
assert_se(s);
log_info("formatted normally: %s\n", s);
r = json_parse(data, &w, NULL, NULL);
assert_se(r == 0);
assert_se(w);
assert_se(json_variant_has_type(v, json_variant_type(w)));
assert_se(json_variant_has_type(w, json_variant_type(v)));
assert_se(json_variant_equal(v, w));
s = mfree(s);
w = json_variant_unref(w);
r = json_variant_format(v, JSON_FORMAT_PRETTY, &s);
assert_se(r >= 0);
assert_se(s);
log_info("formatted prettily:\n%s", s);
r = json_parse(data, &w, NULL, NULL);
assert_se(r == 0);
assert_se(w);
assert_se(json_variant_has_type(v, json_variant_type(w)));
assert_se(json_variant_has_type(w, json_variant_type(v)));
assert_se(json_variant_equal(v, w));
s = mfree(s);
r = json_variant_format(v, JSON_FORMAT_COLOR, &s);
assert_se(r >= 0);
assert_se(s);
printf("Normal with color: %s\n", s);
s = mfree(s);
r = json_variant_format(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, &s);
assert_se(r >= 0);
assert_se(s);
printf("Pretty with color:\n%s\n", s);
if (test)
test(v);
}
static void test_1(JsonVariant *v) {
JsonVariant *p, *q;
unsigned i;
/* 3 keys + 3 values */
assert_se(json_variant_elements(v) == 6);
/* has k */
p = json_variant_by_key(v, "k");
assert_se(p && json_variant_type(p) == JSON_VARIANT_STRING);
/* k equals v */
assert_se(streq(json_variant_string(p), "v"));
/* has foo */
p = json_variant_by_key(v, "foo");
assert_se(p && json_variant_type(p) == JSON_VARIANT_ARRAY && json_variant_elements(p) == 3);
/* check foo[0] = 1, foo[1] = 2, foo[2] = 3 */
for (i = 0; i < 3; ++i) {
q = json_variant_by_index(p, i);
assert_se(q && json_variant_type(q) == JSON_VARIANT_UNSIGNED && json_variant_unsigned(q) == (i+1));
assert_se(q && json_variant_has_type(q, JSON_VARIANT_INTEGER) && json_variant_integer(q) == (i+1));
}
/* has bar */
p = json_variant_by_key(v, "bar");
assert_se(p && json_variant_type(p) == JSON_VARIANT_OBJECT && json_variant_elements(p) == 2);
/* zap is null */
q = json_variant_by_key(p, "zap");
assert_se(q && json_variant_type(q) == JSON_VARIANT_NULL);
}
static void test_2(JsonVariant *v) {
JsonVariant *p, *q;
/* 2 keys + 2 values */
assert_se(json_variant_elements(v) == 4);
/* has mutant */
p = json_variant_by_key(v, "mutant");
assert_se(p && json_variant_type(p) == JSON_VARIANT_ARRAY && json_variant_elements(p) == 4);
/* mutant[0] == 1 */
q = json_variant_by_index(p, 0);
assert_se(q && json_variant_type(q) == JSON_VARIANT_UNSIGNED && json_variant_unsigned(q) == 1);
assert_se(q && json_variant_has_type(q, JSON_VARIANT_INTEGER) && json_variant_integer(q) == 1);
/* mutant[1] == null */
q = json_variant_by_index(p, 1);
assert_se(q && json_variant_type(q) == JSON_VARIANT_NULL);
/* mutant[2] == "1" */
q = json_variant_by_index(p, 2);
assert_se(q && json_variant_type(q) == JSON_VARIANT_STRING && streq(json_variant_string(q), "1"));
/* mutant[3] == JSON_VARIANT_OBJECT */
q = json_variant_by_index(p, 3);
assert_se(q && json_variant_type(q) == JSON_VARIANT_OBJECT && json_variant_elements(q) == 2);
/* has 1 */
p = json_variant_by_key(q, "1");
assert_se(p && json_variant_type(p) == JSON_VARIANT_ARRAY && json_variant_elements(p) == 2);
/* "1"[0] == 1 */
q = json_variant_by_index(p, 0);
assert_se(q && json_variant_type(q) == JSON_VARIANT_UNSIGNED && json_variant_unsigned(q) == 1);
assert_se(q && json_variant_has_type(q, JSON_VARIANT_INTEGER) && json_variant_integer(q) == 1);
/* "1"[1] == "1" */
q = json_variant_by_index(p, 1);
assert_se(q && json_variant_type(q) == JSON_VARIANT_STRING && streq(json_variant_string(q), "1"));
/* has thisisaverylongproperty */
p = json_variant_by_key(v, "thisisaverylongproperty");
assert_se(p && json_variant_type(p) == JSON_VARIANT_REAL && fabs(json_variant_real(p) - 1.27) < 0.001);
}
static void test_zeroes(JsonVariant *v) {
size_t i;
/* Make sure zero is how we expect it. */
assert_se(json_variant_elements(v) == 13);
for (i = 0; i < json_variant_elements(v); i++) {
JsonVariant *w;
size_t j;
assert_se(w = json_variant_by_index(v, i));
assert_se(json_variant_integer(w) == 0);
assert_se(json_variant_unsigned(w) == 0U);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
assert_se(json_variant_real(w) == 0.0L);
#pragma GCC diagnostic pop
assert_se(json_variant_is_integer(w));
assert_se(json_variant_is_unsigned(w));
assert_se(json_variant_is_real(w));
assert_se(json_variant_is_number(w));
assert_se(!json_variant_is_negative(w));
assert_se(IN_SET(json_variant_type(w), JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL));
for (j = 0; j < json_variant_elements(v); j++) {
JsonVariant *q;
assert_se(q = json_variant_by_index(v, j));
assert_se(json_variant_equal(w, q));
}
}
}
static void test_build(void) {
_cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL;
_cleanup_free_ char *s = NULL, *t = NULL;
assert_se(json_build(&a, JSON_BUILD_STRING("hallo")) >= 0);
assert_se(json_build(&b, JSON_BUILD_LITERAL(" \"hallo\" ")) >= 0);
assert_se(json_variant_equal(a, b));
b = json_variant_unref(b);
assert_se(json_build(&b, JSON_BUILD_VARIANT(a)) >= 0);
assert_se(json_variant_equal(a, b));
b = json_variant_unref(b);
assert_se(json_build(&b, JSON_BUILD_STRING("pief")) >= 0);
assert_se(!json_variant_equal(a, b));
a = json_variant_unref(a);
b = json_variant_unref(b);
assert_se(json_build(&a, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("one", JSON_BUILD_INTEGER(7)),
JSON_BUILD_PAIR("two", JSON_BUILD_REAL(2.0)),
JSON_BUILD_PAIR("three", JSON_BUILD_INTEGER(0)))) >= 0);
assert_se(json_build(&b, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("two", JSON_BUILD_INTEGER(2)),
JSON_BUILD_PAIR("three", JSON_BUILD_REAL(0)),
JSON_BUILD_PAIR("one", JSON_BUILD_REAL(7)))) >= 0);
assert_se(json_variant_equal(a, b));
a = json_variant_unref(a);
b = json_variant_unref(b);
assert_se(json_build(&a, JSON_BUILD_ARRAY(JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_BOOLEAN(true)),
JSON_BUILD_PAIR("y", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("this", JSON_BUILD_NULL)))),
JSON_BUILD_VARIANT(NULL),
JSON_BUILD_LITERAL(NULL),
JSON_BUILD_STRING(NULL),
JSON_BUILD_NULL,
JSON_BUILD_INTEGER(77),
JSON_BUILD_STRV(STRV_MAKE("one", "two", "three", "four")))) >= 0);
assert_se(json_variant_format(a, 0, &s) >= 0);
log_info("GOT: %s\n", s);
assert_se(json_parse(s, &b, NULL, NULL) >= 0);
assert_se(json_variant_equal(a, b));
a = json_variant_unref(a);
b = json_variant_unref(b);
assert_se(json_build(&a, JSON_BUILD_REAL(M_PIl)) >= 0);
s = mfree(s);
assert_se(json_variant_format(a, 0, &s) >= 0);
log_info("GOT: %s\n", s);
assert_se(json_parse(s, &b, NULL, NULL) >= 0);
assert_se(json_variant_format(b, 0, &t) >= 0);
log_info("GOT: %s\n", t);
assert_se(streq(s, t));
a = json_variant_unref(a);
b = json_variant_unref(b);
}
static void test_source(void) {
static const char data[] =
"\n"
"\n"
"{\n"
"\"foo\" : \"bar\", \n"
"\"qüüx\" : [ 1, 2, 3,\n"
"4,\n"
"5 ],\n"
"\"miep\" : { \"hallo\" : 1 },\n"
"\n"
"\"zzzzzz\" \n"
":\n"
"[ true, \n"
"false, 7.5, {} ]\n"
"}\n";
_cleanup_fclose_ FILE *f = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
printf("--- original begin ---\n"
"%s"
"--- original end ---\n", data);
assert_se(f = fmemopen((void*) data, sizeof(data), "r"));
assert_se(json_parse_file(f, "waldo", &v, NULL, NULL) >= 0);
printf("--- non-pretty begin ---\n");
json_variant_dump(v, 0, stdout, NULL);
printf("\n--- non-pretty end ---\n");
printf("--- pretty begin ---\n");
json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, stdout, NULL);
printf("--- pretty end ---\n");
}
int main(int argc, char *argv[]) {
log_set_max_level(LOG_DEBUG);
log_parse_environment();
log_open();
test_tokenizer("x", -EINVAL);
test_tokenizer("", JSON_TOKEN_END);
test_tokenizer(" ", JSON_TOKEN_END);
test_tokenizer("0", JSON_TOKEN_UNSIGNED, (uintmax_t) 0, JSON_TOKEN_END);
test_tokenizer("-0", JSON_TOKEN_INTEGER, (intmax_t) 0, JSON_TOKEN_END);
test_tokenizer("1234", JSON_TOKEN_UNSIGNED, (uintmax_t) 1234, JSON_TOKEN_END);
test_tokenizer("-1234", JSON_TOKEN_INTEGER, (intmax_t) -1234, JSON_TOKEN_END);
test_tokenizer("18446744073709551615", JSON_TOKEN_UNSIGNED, (uintmax_t) UINT64_MAX, JSON_TOKEN_END);
test_tokenizer("-9223372036854775808", JSON_TOKEN_INTEGER, (intmax_t) INT64_MIN, JSON_TOKEN_END);
test_tokenizer("18446744073709551616", JSON_TOKEN_REAL, (long double) 18446744073709551616.0L, JSON_TOKEN_END);
test_tokenizer("-9223372036854775809", JSON_TOKEN_REAL, (long double) -9223372036854775809.0L, JSON_TOKEN_END);
test_tokenizer("-1234", JSON_TOKEN_INTEGER, (intmax_t) -1234, JSON_TOKEN_END);
test_tokenizer("3.141", JSON_TOKEN_REAL, (long double) 3.141, JSON_TOKEN_END);
test_tokenizer("0.0", JSON_TOKEN_REAL, (long double) 0.0, JSON_TOKEN_END);
test_tokenizer("7e3", JSON_TOKEN_REAL, (long double) 7e3, JSON_TOKEN_END);
test_tokenizer("-7e-3", JSON_TOKEN_REAL, (long double) -7e-3, JSON_TOKEN_END);
test_tokenizer("true", JSON_TOKEN_BOOLEAN, true, JSON_TOKEN_END);
test_tokenizer("false", JSON_TOKEN_BOOLEAN, false, JSON_TOKEN_END);
test_tokenizer("null", JSON_TOKEN_NULL, JSON_TOKEN_END);
test_tokenizer("{}", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END);
test_tokenizer("\t {\n} \n", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END);
test_tokenizer("[]", JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_END);
test_tokenizer("\t [] \n\n", JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_END);
test_tokenizer("\"\"", JSON_TOKEN_STRING, "", JSON_TOKEN_END);
test_tokenizer("\"foo\"", JSON_TOKEN_STRING, "foo", JSON_TOKEN_END);
test_tokenizer("\"foo\\nfoo\"", JSON_TOKEN_STRING, "foo\nfoo", JSON_TOKEN_END);
test_tokenizer("{\"foo\" : \"bar\"}", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_STRING, "foo", JSON_TOKEN_COLON, JSON_TOKEN_STRING, "bar", JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END);
test_tokenizer("{\"foo\" : [true, false]}", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_STRING, "foo", JSON_TOKEN_COLON, JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_BOOLEAN, true, JSON_TOKEN_COMMA, JSON_TOKEN_BOOLEAN, false, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END);
test_tokenizer("\"\xef\xbf\xbd\"", JSON_TOKEN_STRING, "\xef\xbf\xbd", JSON_TOKEN_END);
test_tokenizer("\"\\ufffd\"", JSON_TOKEN_STRING, "\xef\xbf\xbd", JSON_TOKEN_END);
test_tokenizer("\"\\uf\"", -EINVAL);
test_tokenizer("\"\\ud800a\"", -EINVAL);
test_tokenizer("\"\\udc00\\udc00\"", -EINVAL);
test_tokenizer("\"\\ud801\\udc37\"", JSON_TOKEN_STRING, "\xf0\x90\x90\xb7", JSON_TOKEN_END);
test_tokenizer("[1, 2, -3]", JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_UNSIGNED, (uintmax_t) 1, JSON_TOKEN_COMMA, JSON_TOKEN_UNSIGNED, (uintmax_t) 2, JSON_TOKEN_COMMA, JSON_TOKEN_INTEGER, (intmax_t) -3, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_END);
test_variant("{\"k\": \"v\", \"foo\": [1, 2, 3], \"bar\": {\"zap\": null}}", test_1);
test_variant("{\"mutant\": [1, null, \"1\", {\"1\": [1, \"1\"]}], \"thisisaverylongproperty\": 1.27}", test_2);
test_variant("{\"foo\" : \"\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFFFFF\\\"\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFF\\uDBFF\\uDFFFF\\uDBFF\\uDFFF\\uDBFF\\uDFFF\\uDBFF\\uDFFF\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFFFF\\\"\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFF\\uDBFF\\uDFFF\"}", NULL);
test_variant("[ 0, -0, 0.0, -0.0, 0.000, -0.000, 0e0, -0e0, 0e+0, -0e-0, 0e-0, -0e000, 0e+000 ]", test_zeroes);
test_build();
test_source();
return 0;
}

View File

@ -48,5 +48,8 @@ zip -jqr $OUT/fuzz-dns-packet_seed_corpus.zip $df/packet
install -Dt $OUT/src/shared/ $build/src/shared/libsystemd-shared-*.so
wget -O $OUT/fuzz-json_seed_corpus.zip https://storage.googleapis.com/skia-fuzzer/oss-fuzz/skjson_seed_corpus.zip
wget -O $OUT/fuzz-json.dict https://raw.githubusercontent.com/rc0r/afl-fuzz/master/dictionaries/json.dict
find $build -maxdepth 1 -type f -executable -name "fuzz-*" -exec mv {} $OUT \;
cp src/fuzz/*.options $OUT