From 03f897e5749039cfdff3230aafe11537baf6982b Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Sat, 3 Jul 2021 17:42:15 -0800 Subject: [PATCH 01/11] Tests that try to show that can't get/add keys with embedded null characters --- json-c.sym | 7 +- json_object.c | 30 ++++++- json_object.h | 104 ++++++++++++++++++++-- tests/CMakeLists.txt | 2 + tests/test_null_keys_add.c | 123 ++++++++++++++++++++++++++ tests/test_null_keys_add.expected | 6 ++ tests/test_null_keys_add.test | 1 + tests/test_null_keys_get.c | 139 ++++++++++++++++++++++++++++++ tests/test_null_keys_get.expected | 6 ++ tests/test_null_keys_get.test | 1 + 10 files changed, 408 insertions(+), 11 deletions(-) create mode 100644 tests/test_null_keys_add.c create mode 100644 tests/test_null_keys_add.expected create mode 120000 tests/test_null_keys_add.test create mode 100644 tests/test_null_keys_get.c create mode 100644 tests/test_null_keys_get.expected create mode 120000 tests/test_null_keys_get.test diff --git a/json-c.sym b/json-c.sym index 2dedc6e..71c0585 100644 --- a/json-c.sym +++ b/json-c.sym @@ -165,6 +165,9 @@ JSONC_0.15 { } JSONC_0.14; JSONC_0.16 { -# global: -# ...new symbols here... + global: + json_object_object_add_len; + json_object_object_add_ex_len; + json_object_object_get_len; + json_object_object_get_ex_len; } JSONC_0.15; diff --git a/json_object.c b/json_object.c index c15a477..544542f 100644 --- a/json_object.c +++ b/json_object.c @@ -582,6 +582,12 @@ struct lh_table *json_object_get_object(const struct json_object *jso) int json_object_object_add_ex(struct json_object *jso, const char *const key, struct json_object *const val, const unsigned opts) +{ + return json_object_object_add_ex_len(jso, key, strlen(key), val, opts); +} + +int json_object_object_add_ex_len(struct json_object *jso, const char *const key, const int len, + struct json_object *const val, const unsigned opts) { struct json_object *existing_value = NULL; struct lh_entry *existing_entry; @@ -619,7 +625,13 @@ int json_object_object_add_ex(struct json_object *jso, const char *const key, int json_object_object_add(struct json_object *jso, const char *key, struct json_object *val) { - return json_object_object_add_ex(jso, key, val, 0); + return json_object_object_add_ex_len(jso, key, strlen(key), val, 0); +} + +int json_object_object_add_len(struct json_object *jso, const char *key, const int len, + struct json_object *val) +{ + return json_object_object_add_ex_len(jso, key, len, val, 0); } int json_object_object_length(const struct json_object *jso) @@ -636,12 +648,26 @@ size_t json_c_object_sizeof(void) struct json_object *json_object_object_get(const struct json_object *jso, const char *key) { struct json_object *result = NULL; - json_object_object_get_ex(jso, key, &result); + json_object_object_get_ex_len(jso, key, strlen(key), &result); + return result; +} + +struct json_object *json_object_object_get_len(const struct json_object *jso, const char *key, + const int len) +{ + struct json_object *result = NULL; + json_object_object_get_ex_len(jso, key, len, &result); return result; } json_bool json_object_object_get_ex(const struct json_object *jso, const char *key, struct json_object **value) +{ + return json_object_object_get_ex_len(jso, key, strlen(key), value); +} + +json_bool json_object_object_get_ex_len(const struct json_object *jso, const char *key, + const int len, struct json_object **value) { if (value != NULL) *value = NULL; diff --git a/json_object.h b/json_object.h index d67f384..e3d4d56 100644 --- a/json_object.h +++ b/json_object.h @@ -385,6 +385,36 @@ JSON_C_CONST_FUNCTION(JSON_EXPORT size_t json_c_object_sizeof(void)); JSON_EXPORT int json_object_object_add(struct json_object *obj, const char *key, struct json_object *val); +/** Add an object field to a json_object of type json_type_object + * + * The reference count of `val` will *not* be incremented, in effect + * transferring ownership that object to `obj`, and thus `val` will be + * freed when `obj` is. (i.e. through `json_object_put(obj)`) + * + * If you want to retain a reference to the added object, independent + * of the lifetime of obj, you must increment the refcount with + * `json_object_get(val)` (and later release it with json_object_put()). + * + * Since ownership transfers to `obj`, you must make sure + * that you do in fact have ownership over `val`. For instance, + * json_object_new_object() will give you ownership until you transfer it, + * whereas json_object_object_get() does not. + * + * Any previous object stored under `key` in `obj` will have its refcount + * decremented, and be freed normally if that drops to zero. + * + * @param obj the json_object instance + * @param key the object field name (a private copy will be duplicated), + * which is not terminated by a NULL ( @c '\0' ) character + * @param len the length of @p key + * @param val a json_object or NULL member to associate with the given field + * + * @return On success, @c 0 is returned. + * On error, a negative value is returned. + */ +JSON_EXPORT int json_object_object_add_len(struct json_object *obj, const char *key, const int len, + struct json_object *val); + /** Add an object field to a json_object of type json_type_object * * The semantics are identical to json_object_object_add, except that an @@ -397,13 +427,51 @@ JSON_EXPORT int json_object_object_add(struct json_object *obj, const char *key, * @param val a json_object or NULL member to associate with the given field * @param opts process-modifying options. To specify multiple options, use * (OPT1|OPT2) + * + * @return On success, @c 0 is returned. + * On error, a negative value is returned. */ JSON_EXPORT int json_object_object_add_ex(struct json_object *obj, const char *const key, struct json_object *const val, const unsigned opts); +/** Add an object field to a json_object of type json_type_object + * + * The semantics are identical to json_object_object_add, except that an + * additional flag fields gives you more control over some detail aspects + * of processing. See the description of JSON_C_OBJECT_ADD_* flags for more + * details. + * + * @param obj the json_object instance + * @param key the object field name (a private copy will be duplicated), + * which is not terminated by a NULL ( @c '\0' ) character + * @param len the length of @p key + * @param val a json_object or NULL member to associate with the given field + * @param opts process-modifying options. To specify multiple options, use + * (OPT1|OPT2) + * + * @return On success, @c 0 is returned. + * On error, a negative value is returned. + */ +JSON_EXPORT int json_object_object_add_ex_len(struct json_object *obj, const char *const key, + const int len, struct json_object *const val, + const unsigned opts); + /** Get the json_object associate with a given object field. * Deprecated/discouraged: used json_object_object_get_ex instead. * + * This functions exactly like calling + * @code json_object_object_get_len(obj, key, strlen(key)) @endcode + * + * @param obj the json_object instance + * @param key the object field name + * @returns the json_object associated with the given field name + * @see json_object_object_get_len() + */ +JSON_EXPORT struct json_object *json_object_object_get(const struct json_object *obj, + const char *key); +/** Get the json_object associate with a given object field. + * Deprecated/discouraged: used json_object_object_get_ex_len instead. + * * This returns NULL if the field is found but its value is null, or if * the field is not found, or if obj is not a json_type_object. If you * need to distinguish between these cases, use json_object_object_get_ex(). @@ -419,11 +487,31 @@ JSON_EXPORT int json_object_object_add_ex(struct json_object *obj, const char *c * or transfer ownership to prevent a memory leak). * * @param obj the json_object instance - * @param key the object field name + * @param key the object field name, + * which is not terminated by a NULL ( @c '\0' ) character + * @param len the length of @p key * @returns the json_object associated with the given field name */ -JSON_EXPORT struct json_object *json_object_object_get(const struct json_object *obj, - const char *key); + +JSON_EXPORT struct json_object *json_object_object_get_len(const struct json_object *obj, + const char *key, const int len); + +/** Get the json_object associated with a given object field. + * + * This functions exactly like calling + * @code json_object_object_get_ex_len(obj, key, strlen(key), value) @endcode + * + * @param obj the json_object instance + * @param key the object field name + * @param value a pointer where to store a reference to the json_object + * associated with the given field name. + * \n + * It is safe to pass a NULL value. + * @returns whether or not the key exists + * @see json_object_object_get_ex_len() + */ +JSON_EXPORT json_bool json_object_object_get_ex(const struct json_object *obj, const char *key, + struct json_object **value); /** Get the json_object associated with a given object field. * @@ -436,15 +524,17 @@ JSON_EXPORT struct json_object *json_object_object_get(const struct json_object * than the owning parent (obj). Ownership of value is retained by obj. * * @param obj the json_object instance - * @param key the object field name + * @param key the object field name, + * which is not terminated by a NULL ( @c '\0' ) character + * @param len the length of @p key * @param value a pointer where to store a reference to the json_object * associated with the given field name. - * + * \n * It is safe to pass a NULL value. * @returns whether or not the key exists */ -JSON_EXPORT json_bool json_object_object_get_ex(const struct json_object *obj, const char *key, - struct json_object **value); +JSON_EXPORT json_bool json_object_object_get_ex_len(const struct json_object *obj, const char *key, + const int len, struct json_object **value); /** Delete the given json_object field * diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d7abf51..7d84442 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,6 +26,8 @@ set(ALL_TEST_NAMES test_int_add test_locale test_null + test_null_keys_add + test_null_keys_get test_parse test_parse_int64 test_printbuf diff --git a/tests/test_null_keys_add.c b/tests/test_null_keys_add.c new file mode 100644 index 0000000..ce31582 --- /dev/null +++ b/tests/test_null_keys_add.c @@ -0,0 +1,123 @@ +/* + * Tests if binary strings are supported. + */ + +#include "config.h" +#include +#include + +#include "json_inttypes.h" +#include "json_object.h" +#include "json_tokener.h" + +int main(void) +{ + /* this test has embedded null characters in the key and value. + * check that it's still included after deserializing. */ + const char *input = "{ \"foo\": 14.5, \"bar\": [] }"; + const char *foo_key = "foo"; + const char *foo_value = "14.5"; + const char *bar_key = "bar"; + + const char *toadd_key = "foo\0bar"; + const int toadd_key_len = 7; + const char *toadd_key_printable = "foo\\0bar"; + const char *toadd_value = "qwerty\0asdf"; + const int toadd_value_len = 12; + const char *toadd_value_printable = "qwerty\\0asdf"; + + struct json_object *parsed = json_tokener_parse(input); + + printf("Parsed input: %s\n", input); + printf("Result is "); + if (parsed == NULL) + { + printf("NULL (error!)\n"); + return 1; // Error somewhere + } + else if (!json_object_is_type(parsed, json_type_object)) + { + printf("not `json_type_object` (error!)\n"); + return 1; // Error somewhere + } + else + { + printf("`json_type_object`\n"); + } + + // Check nothing odd happened in parsing + if (json_object_object_length(parsed) != 2) + { + printf("Should contain two fields (has %d) (error!)", + json_object_object_length(parsed)); + return 1; + } + json_bool key_present = json_object_object_get_ex(parsed, foo_key, NULL); + if (!key_present) + { + printf("Should contain key \"%s\", but does not (error!)", foo_key); + return 1; + } + key_present = json_object_object_get_ex(parsed, bar_key, NULL); + if (!key_present) + { + printf("Should contain key \"%s\", but does not (error!)", bar_key); + return 1; + } + + // Add the new key + struct json_object *new_str = json_object_new_string_len(toadd_value, toadd_value_len); + if (json_object_object_add_ex(parsed, toadd_key, new_str, + JSON_C_OBJECT_ADD_KEY_IS_NEW | + JSON_C_OBJECT_KEY_IS_CONSTANT) != 0) + { + printf("An error occured adding the key \"%s\" (error!)\n", toadd_key_printable); + return 1; + } + + // Check the new key was actually added + if (json_object_object_length(parsed) != 3) + { + printf("Should contain three fields after adding new key (has %d) (error!)", + json_object_object_length(parsed)); + return 1; + } + else if (json_object_object_get_len(parsed, toadd_key, toadd_key_len) != new_str) + { + printf("Have three keys, but don't have the right value in \"%s\" (error!)\n", + toadd_key_printable); + return 1; + } + else + { + printf("Added the key \"%s\" successfully\n", toadd_key_printable); + } + + // Check the previous keys are still the same present + struct json_object *foo = json_object_object_get(parsed, foo_key); + if (!json_object_is_type(foo, json_type_double)) + { + printf("Key \"%s\" should be `json_type_double` (%d) but was %d (error!)\n", + foo_key, (int)json_type_double, json_object_get_type(foo)); + return 1; + } + else + { + printf("Key \"%s\" is still the same\n", foo_key); + } + struct json_object *bar = json_object_object_get(parsed, bar_key); + if (!json_object_is_type(foo, json_type_array)) + { + printf("Key \"%s\" should be `json_type_array` (%d) but was %d (error!)\n", bar_key, + (int)json_type_array, json_object_get_type(foo)); + return 1; + } + else + { + printf("Key \"%s\" is still the same\n", bar_key); + } + + json_object_put(parsed); + printf("PASS\n"); + return 0; +} diff --git a/tests/test_null_keys_add.expected b/tests/test_null_keys_add.expected new file mode 100644 index 0000000..5e9a9c1 --- /dev/null +++ b/tests/test_null_keys_add.expected @@ -0,0 +1,6 @@ +Parsed input: { "foo": 14.5, "bar": [] } +Result is `json_type_object` +Added the key "foo\0bar" successfully +Key "foo" is still the same +Key "bar" is still the same +PASS diff --git a/tests/test_null_keys_add.test b/tests/test_null_keys_add.test new file mode 120000 index 0000000..58a13f4 --- /dev/null +++ b/tests/test_null_keys_add.test @@ -0,0 +1 @@ +test_basic.test \ No newline at end of file diff --git a/tests/test_null_keys_get.c b/tests/test_null_keys_get.c new file mode 100644 index 0000000..d63f579 --- /dev/null +++ b/tests/test_null_keys_get.c @@ -0,0 +1,139 @@ +/* + * Tests if binary strings are supported. + */ + +#include "config.h" +#include +#include + +#include "json_inttypes.h" +#include "json_object.h" +#include "json_tokener.h" + +int main(void) +{ + /* this test has embedded null characters in the key and value. + * check that it's still included after deserializing. */ + const char *input = "{ \"foo\\u0000bar\": \"qwerty\\u0000asdf\" }"; + const char *expected_key = "foo\0bar"; + const int expected_key_len = 7; + const char *expected_key_printable = "foo\\0bar"; + const char *expected_value = "qwerty\0asdf"; + const int expected_value_len = 12; + const char *expected_value_printable = "qwerty\\0asdf"; + + struct json_object *parsed = json_tokener_parse(input); + + printf("Parsed input: %s\n", input); + printf("Result is "); + if (parsed == NULL) + { + printf("NULL (error!)\n"); + return 1; // Error somewhere + } + else if (!json_object_is_type(parsed, json_type_object)) + { + printf("not `json_type_object` (error!)\n"); + return 1; // Error somewhere + } + else + { + printf("`json_type_object`\n"); + } + + // Check nothing odd happened in parsing + if (json_object_object_length(parsed) != 1) + { + printf("Should contain only one field (has %d) (error!)", + json_object_object_length(parsed)); + return 1; + } + json_bool key_present = json_object_object_get_ex(parsed, expected_key, NULL); + if (key_present) + { + printf("The key \"%s\" should not be present when calling " + "`json_object_object_get_ex` " + "(the real key contains a NUL character). (error!)\n", + expected_key); + return 1; + } + + // Check the key is present + struct json_object *string; + key_present = json_object_object_get_ex_len(parsed, expected_key, 7, &string); + if (!key_present) + { + printf("The key \"%s\" should be present when calling " + "`json_object_object_get_ex_len` (error!)\n", + expected_key_printable); + return 1; + } + else if (string == NULL) + { + printf("The key \"%s\" was present when calling " + "`json_object_object_get_ex_len`, but got NULL (error!)\n", + expected_key_printable); + return 1; + } + struct json_object *from_get_len = json_object_object_get_len(parsed, expected_key, 7); + if (string != from_get_len) + { + printf("The value returned from `json_object_object_get_len` should be the " + "same as the one from `json_object_object_get_ex_len` (error!)\n" + "Got %p from `json_object_object_get_ex_len` but %p from " + "`json_object_object_get_len`\n", + string, from_get_len); + return 1; + } + else + { + printf("Key was present and same for " + "`json_object_object_get_ex_len` and `json_object_object_get_len`\n"); + } + + // Check the value is right + if (!json_object_is_type(string, json_type_string)) + { + printf( + "Value is wrong type, expected `json_type_string` (%d) but got %d (error!)\n", + json_type_string, json_object_get_type(string)); + return 1; + } + else + { + printf("Value is right type, `json_type_string`\n"); + } + const int actual_value_len = json_object_get_string_len(string); + const char *actual_value = json_object_get_string(string); + if (actual_value_len != expected_value_len) + { + printf("Value is wrong length, expected %d but got %d (error!)\n", + expected_value_len, actual_value_len); + return 1; + } + else if (memcmp(expected_value, actual_value, actual_value_len) != 0) + { + printf("Expected \"%s\" but got \"", expected_value_printable); + for (int i = 0; i < actual_value_len; ++i) + { + if (actual_value[i] == '\0') + { + puts("\\0"); + } + else + { + putchar(actual_value[i]); + } + } + printf("\"\n"); + return 1; + } + else + { + printf("Expected value matches actual\n"); + } + + json_object_put(parsed); + printf("PASS\n"); + return 0; +} diff --git a/tests/test_null_keys_get.expected b/tests/test_null_keys_get.expected new file mode 100644 index 0000000..6a3c86d --- /dev/null +++ b/tests/test_null_keys_get.expected @@ -0,0 +1,6 @@ +Parsed input: { "foo\u0000bar": "qwerty\u0000asdf" } +Result is `json_type_object` +Key was present and same for `json_object_object_get_ex_len` and `json_object_object_get_len` +Value is right type, `json_type_string` +Expected value matches actual +PASS diff --git a/tests/test_null_keys_get.test b/tests/test_null_keys_get.test new file mode 120000 index 0000000..58a13f4 --- /dev/null +++ b/tests/test_null_keys_get.test @@ -0,0 +1 @@ +test_basic.test \ No newline at end of file From f230b25586d4a5604a0fde7c28b694ad94a69e80 Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Sun, 4 Jul 2021 01:05:59 -0800 Subject: [PATCH 02/11] Changes to the hash table so add works (kinda not-really) --- json_object.c | 65 +++++++++++++++++++------------------- json_object_private.h | 3 ++ linkhash.c | 51 ++++++++++++++++++++++++++++-- linkhash.h | 72 ++++++++++++++++++++++++++++++++++++++++++- math_compat.h | 13 ++++++++ 5 files changed, 169 insertions(+), 35 deletions(-) diff --git a/json_object.c b/json_object.c index 544542f..ad40fa9 100644 --- a/json_object.c +++ b/json_object.c @@ -39,18 +39,6 @@ #error "The long long type isn't 64-bits" #endif -#ifndef SSIZE_T_MAX -#if SIZEOF_SSIZE_T == SIZEOF_INT -#define SSIZE_T_MAX INT_MAX -#elif SIZEOF_SSIZE_T == SIZEOF_LONG -#define SSIZE_T_MAX LONG_MAX -#elif SIZEOF_SSIZE_T == SIZEOF_LONG_LONG -#define SSIZE_T_MAX LLONG_MAX -#else -#error Unable to determine size of ssize_t -#endif -#endif - // Don't define this. It's not thread-safe. /* #define REFCOUNT_DEBUG 1 */ @@ -589,38 +577,53 @@ int json_object_object_add_ex(struct json_object *jso, const char *const key, int json_object_object_add_ex_len(struct json_object *jso, const char *const key, const int len, struct json_object *const val, const unsigned opts) { - struct json_object *existing_value = NULL; + struct json_object *existing_value; struct lh_entry *existing_entry; unsigned long hash; + /** Required due to the `lh_get_hash` function wanting a `const struct lh_string *` */ + const struct lh_string hashable = {.length = len, .str = {.pdata = key}}; assert(json_object_get_type(jso) == json_type_object); + // The caller must avoid creating loops in the object tree, but do a + // quick check anyway to make sure we're not creating a trivial loop. + if (jso == val) + { + return -1; + } + // We lookup the entry and replace the value, rather than just deleting // and re-adding it, so the existing key remains valid. - hash = lh_get_hash(JC_OBJECT(jso)->c_object, (const void *)key); + hash = lh_get_hash(JC_OBJECT(jso)->c_object, (const void *)&hashable); existing_entry = (opts & JSON_C_OBJECT_ADD_KEY_IS_NEW) ? NULL : lh_table_lookup_entry_w_hash(JC_OBJECT(jso)->c_object, (const void *)key, hash); - // The caller must avoid creating loops in the object tree, but do a - // quick check anyway to make sure we're not creating a trivial loop. - if (jso == val) - return -1; - - if (!existing_entry) + if (existing_entry == NULL) { - const void *const k = - (opts & JSON_C_OBJECT_KEY_IS_CONSTANT) ? (const void *)key : strdup(key); + const struct lh_string *k = (opts & JSON_C_OBJECT_KEY_IS_CONSTANT) + ? lh_string_new_ptr(len, key) + : lh_string_new_imm(len, key); if (k == NULL) + { return -1; - return lh_table_insert_w_hash(JC_OBJECT(jso)->c_object, k, val, hash, opts); + } + return lh_table_insert_w_hash(JC_OBJECT(jso)->c_object, k, val, hash, + opts & ~JSON_C_OBJECT_KEY_IS_CONSTANT); + // `struct lh_string` always needs to be freed, + // so JSON_C_OBJECT_KEY_IS_CONSTANT cannot be set + } + else + { + existing_value = (json_object *)lh_entry_v(existing_entry); + if (existing_value) + { + json_object_put(existing_value); + } + existing_entry->v = val; + return 0; } - existing_value = (json_object *)lh_entry_v(existing_entry); - if (existing_value) - json_object_put(existing_value); - existing_entry->v = val; - return 0; } int json_object_object_add(struct json_object *jso, const char *key, struct json_object *val) @@ -761,7 +764,7 @@ struct json_object *json_object_new_int(int32_t i) int32_t json_object_get_int(const struct json_object *jso) { - int64_t cint64=0; + int64_t cint64 = 0; double cdouble; enum json_type o_type; @@ -1751,8 +1754,8 @@ static int json_object_deep_copy_recursive(struct json_object *src, struct json_ /* This handles the `json_type_null` case */ if (!iter.val) jso = NULL; - else if (json_object_deep_copy_recursive(iter.val, src, iter.key, UINT_MAX, &jso, - shallow_copy) < 0) + else if (json_object_deep_copy_recursive(iter.val, src, iter.key, UINT_MAX, + &jso, shallow_copy) < 0) { json_object_put(jso); return -1; diff --git a/json_object_private.h b/json_object_private.h index e143b46..4f5e7c4 100644 --- a/json_object_private.h +++ b/json_object_private.h @@ -98,6 +98,9 @@ struct json_object_string void _json_c_set_last_err(const char *err_fmt, ...); +/** + * The characters that can make up hexadecimal numbers + */ extern const char *json_hex_chars; #ifdef __cplusplus diff --git a/linkhash.c b/linkhash.c index b021ef1..36dd014 100644 --- a/linkhash.c +++ b/linkhash.c @@ -30,6 +30,7 @@ #endif #include "linkhash.h" +#include "math_compat.h" #include "random_seed.h" /* hash functions */ @@ -485,13 +486,57 @@ static unsigned long lh_char_hash(const void *k) random_seed = seed; /* potentially racy */ #endif } - - return hashlittle((const char *)k, strlen((const char *)k), random_seed); + return hashlittle(lh_string_data((const struct lh_string *)k), + lh_string_size((const struct lh_string *)k), random_seed); } int lh_char_equal(const void *k1, const void *k2) { - return (strcmp((const char *)k1, (const char *)k2) == 0); + return lh_string_size(k1) == lh_string_size(k2) && + memcmp(lh_string_data(k1), lh_string_data(k2), lh_string_size(k1)) == 0; +} + +const char *lh_string_data(const struct lh_string *str) +{ + return (str->length > 0) ? str->str.pdata : str->str.idata; +} + +size_t lh_string_size(const struct lh_string *str) +{ + return (str->length > 0) ? (size_t)str->length : (size_t)(-(str->length)); +} + +const struct lh_string *lh_string_new_ptr(const size_t length, const char *data) +{ + struct lh_string *result = malloc(sizeof(struct lh_string)); + if (result == NULL) + { + return NULL; + } + result->length = length; + result->str.pdata = data; + return result; +} + +const struct lh_string *lh_string_new_imm(const size_t length, const char *data) +{ + struct lh_string *result; + if (length > + SSIZE_T_MAX - (sizeof(struct lh_string) - sizeof(((struct lh_string *)NULL)->str)) - 1) + { + return NULL; + } + result = + malloc(sizeof(struct lh_string) - sizeof(((struct lh_string *)NULL)->str) + length + 1); + if (result == NULL) + { + return NULL; + } + result->length = -length; + char *unconst = _LH_UNCONST(result->str.idata); + memcpy(unconst, data, length); + unconst = '\0'; + return result; } struct lh_table *lh_table_new(int size, lh_entry_free_fn *free_fn, lh_hash_fn *hash_fn, diff --git a/linkhash.h b/linkhash.h index 0ddfa54..e304117 100644 --- a/linkhash.h +++ b/linkhash.h @@ -79,6 +79,34 @@ typedef unsigned long(lh_hash_fn)(const void *k); */ typedef int(lh_equal_fn)(const void *k1, const void *k2); +/** + * @brief A buffer of characters that may contain null charaters in the middle + * + * A buffer of data that can hold a normal null-terminated string + * (in which case `length` should just be equal to `strlen`) + * or a string with embedded null characters (in which case `length` reflects + * all the characters that make up the "string"). + * Either way, this struct can be treated as if it contains null characters, + * since the `length` member should always be equal to the proper size of the + * buffer and the terminating null character wouldn't be included + * (it wouldn't be counted by strlen). + */ +struct lh_string +{ + /** + * @brief Stores the length of the buffer + * + * If the length is positive, then `pdata` should be used. + * Otherwise, idata should be used. + */ + ssize_t length; + union + { + const char *pdata; + const char idata[0]; + } str; +}; + /** * An entry in the hash table */ @@ -137,7 +165,14 @@ struct lh_table * A pointer onto the function responsible for freeing an entry. */ lh_entry_free_fn *free_fn; + /** + * A function that is capable of hashing entries in this table + */ lh_hash_fn *hash_fn; + /** + * A function that is capable of determining if entries in this table + * are equal + */ lh_equal_fn *equal_fn; }; typedef struct lh_table lh_table; @@ -157,6 +192,41 @@ typedef struct lh_table lh_table; #define lh_foreach_safe(table, entry, tmp) \ for (entry = table->head; entry && ((tmp = entry->next) || 1); entry = tmp) +/** + * @brief Get the data from a `struct lh_string *` + * + * @param str value to retrieve the data from + */ +extern const char *lh_string_data(const struct lh_string *str); + +/** + * @brief Get the length of the data stored in a `struct lh_string *` + * + * @param str value to retrieve the length of + */ +extern size_t lh_string_size(const struct lh_string *str); + +/** + * @brief Creates a new `struct lh_string` using the `pdata` field. + * + * This avoids unneeded copying, for cases wher ethe key is in an area of + * memory that will not be modified or freed until after this object is freed. + * + * @param length The length of the data located at @p data + * @param data The data to include + * @return `NULL` on error + */ +extern const struct lh_string *lh_string_new_ptr(const size_t length, const char *data); + +/** + * @brief Creates a new `struct lh_string` using the `idata` field. + * + * @param length The length of the data to copy into the returned value + * @param data The data to include and copy into the returned value + * @return `NULL` on error + */ +extern const struct lh_string *lh_string_new_imm(const size_t length, const char *data); + /** * Create a new linkhash table. * @@ -311,7 +381,7 @@ int lh_table_resize(struct lh_table *t, int new_size); /** * @deprecated Don't use this outside of linkhash.h: */ -#if (defined(AIX_CC) || (defined(_MSC_VER) && (_MSC_VER <= 1800)) ) +#if (defined(AIX_CC) || (defined(_MSC_VER) && (_MSC_VER <= 1800))) /* VS2010 can't handle inline funcs, so skip it there */ #define _LH_INLINE #else diff --git a/math_compat.h b/math_compat.h index 2382fe1..9894776 100644 --- a/math_compat.h +++ b/math_compat.h @@ -40,4 +40,17 @@ #define HAVE_DECL_NAN #endif +#include +#ifndef SSIZE_T_MAX +#if SIZEOF_SSIZE_T == SIZEOF_INT +#define SSIZE_T_MAX INT_MAX +#elif SIZEOF_SSIZE_T == SIZEOF_LONG +#define SSIZE_T_MAX LONG_MAX +#elif SIZEOF_SSIZE_T == SIZEOF_LONG_LONG +#define SSIZE_T_MAX LLONG_MAX +#else +#error Unable to determine size of ssize_t +#endif +#endif + #endif From 3c0a85c9e8c60b892def49f1f16a1fa22d8d7d10 Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Mon, 5 Jul 2021 17:14:14 -0800 Subject: [PATCH 03/11] Additional changes to json to contain null characters --- json-c.sym | 6 +++- json_object.c | 69 ++++++++++++++++++++++++++++++++++-------- json_object.h | 28 ++++++++++++----- json_object_iterator.c | 4 +-- json_object_iterator.h | 3 +- json_types.h | 2 +- json_visit.c | 4 +-- json_visit.h | 3 +- linkhash.c | 7 ++++- linkhash.h | 9 ++++++ 10 files changed, 106 insertions(+), 29 deletions(-) diff --git a/json-c.sym b/json-c.sym index 71c0585..e69e18b 100644 --- a/json-c.sym +++ b/json-c.sym @@ -3,7 +3,7 @@ * Symbol versioning for libjson-c. * All exported symbols must be listed here. * - * See + * See * https://software.intel.com/sites/default/files/m/a/1/e/dsohowto.pdf */ @@ -166,8 +166,12 @@ JSONC_0.15 { JSONC_0.16 { global: + lh_string_data; + lh_string_size; + lh_string_print; json_object_object_add_len; json_object_object_add_ex_len; + json_object_object_del_len; json_object_object_get_len; json_object_object_get_ex_len; } JSONC_0.15; diff --git a/json_object.c b/json_object.c index ad40fa9..cae67dd 100644 --- a/json_object.c +++ b/json_object.c @@ -116,6 +116,14 @@ static inline const struct json_object_string *JC_STRING_C(const struct json_obj static inline struct json_object *json_object_new(enum json_type o_type, size_t alloc_size, json_object_to_json_string_fn *to_json_string); +static int json_object_object_add_internal(struct json_object *jso, const struct lh_string *key, + struct json_object *const val, const unsigned opts); +static int json_object_object_del_internal(struct json_object *jso_base, + const struct lh_string *key); +static json_bool json_object_object_get_internal(const struct json_object *jso, + const struct lh_string *key, + struct json_object **value); + static void json_object_object_delete(struct json_object *jso_base); static void json_object_string_delete(struct json_object *jso); static void json_object_array_delete(struct json_object *jso); @@ -506,7 +514,7 @@ static int json_object_object_to_json_string(struct json_object *jso, struct pri printbuf_strappend(pb, " "); indent(pb, level + 1, flags); printbuf_strappend(pb, "\""); - json_escape_str(pb, iter.key, strlen(iter.key), flags); + json_escape_str(pb, lh_string_data(iter.key), lh_string_size(iter.key), flags); if (flags & JSON_C_TO_STRING_SPACED) printbuf_strappend(pb, "\": "); else @@ -576,12 +584,21 @@ int json_object_object_add_ex(struct json_object *jso, const char *const key, int json_object_object_add_ex_len(struct json_object *jso, const char *const key, const int len, struct json_object *const val, const unsigned opts) +{ + // Created on the stack rather than calling `lh_string_new_ptr` + // or `lh_string_new_imm` since this saves copying `key` if it turns + // out the value already exists in the hash table + const struct lh_string hashable = {.length = len, .str = {.pdata = key}}; + return json_object_object_add_internal(jso, &hashable, val, opts); +} + +int json_object_object_add_internal(struct json_object *jso, const struct lh_string *key, + struct json_object *const val, const unsigned opts) { struct json_object *existing_value; struct lh_entry *existing_entry; unsigned long hash; /** Required due to the `lh_get_hash` function wanting a `const struct lh_string *` */ - const struct lh_string hashable = {.length = len, .str = {.pdata = key}}; assert(json_object_get_type(jso) == json_type_object); @@ -594,7 +611,7 @@ int json_object_object_add_ex_len(struct json_object *jso, const char *const key // We lookup the entry and replace the value, rather than just deleting // and re-adding it, so the existing key remains valid. - hash = lh_get_hash(JC_OBJECT(jso)->c_object, (const void *)&hashable); + hash = lh_get_hash(JC_OBJECT(jso)->c_object, key); existing_entry = (opts & JSON_C_OBJECT_ADD_KEY_IS_NEW) ? NULL @@ -602,9 +619,15 @@ int json_object_object_add_ex_len(struct json_object *jso, const char *const key if (existing_entry == NULL) { - const struct lh_string *k = (opts & JSON_C_OBJECT_KEY_IS_CONSTANT) - ? lh_string_new_ptr(len, key) - : lh_string_new_imm(len, key); + // need to copy `key` because the caller might have created it + // on the stack, which would be more efficient than copying + // `lh_string_data` into `key->string.idata` if it had happened + // that `existing_entry` wasn't NULL. + const struct lh_string *k = + (opts & JSON_C_OBJECT_KEY_IS_CONSTANT) + ? lh_string_new_ptr(lh_string_size(key), lh_string_data(key)) + : lh_string_new_imm(lh_string_size(key), lh_string_data(key)); + // TODO some optimization where (opts & JSON_C_OBJECT_ADD_KEY_IS_NEW) if (k == NULL) { return -1; @@ -681,8 +704,10 @@ json_bool json_object_object_get_ex_len(const struct json_object *jso, const cha switch (jso->o_type) { case json_type_object: - return lh_table_lookup_ex(JC_OBJECT_C(jso)->c_object, (const void *)key, - (void **)value); + { + const struct lh_string hashable = {.length = len, .str = {.pdata = key}}; + return json_object_object_get_internal(jso, &hashable, value); + } default: if (value != NULL) *value = NULL; @@ -690,10 +715,28 @@ json_bool json_object_object_get_ex_len(const struct json_object *jso, const cha } } +json_bool json_object_object_get_internal(const struct json_object *jso, + const struct lh_string *key, struct json_object **value) +{ + assert(json_object_get_type(jso) == json_type_object); + return lh_table_lookup_ex(JC_OBJECT_C(jso)->c_object, key, (void **)value); +} + void json_object_object_del(struct json_object *jso, const char *key) +{ + json_object_object_del_len(jso, key, strlen(key)); +} + +void json_object_object_del_len(struct json_object *jso, const char *key, const int len) +{ + const struct lh_string hashable = {.length = len, .str = {.pdata = key}}; + json_object_object_del_internal(jso, &hashable); +} + +int json_object_object_del_internal(struct json_object *jso, const struct lh_string *key) { assert(json_object_get_type(jso) == json_type_object); - lh_table_delete(JC_OBJECT(jso)->c_object, key); + return lh_table_delete(JC_OBJECT(jso)->c_object, key); } /* json_object_boolean */ @@ -1680,7 +1723,7 @@ static int json_object_copy_serializer_data(struct json_object *src, struct json * * This always returns -1 or 1. It will never return 2 since it does not copy the serializer. */ -int json_c_shallow_copy_default(json_object *src, json_object *parent, const char *key, +int json_c_shallow_copy_default(json_object *src, json_object *parent, const struct lh_string *key, size_t index, json_object **dst) { switch (src->o_type) @@ -1728,8 +1771,8 @@ int json_c_shallow_copy_default(json_object *src, json_object *parent, const cha * Note: caller is responsible for freeing *dst if this fails and returns -1. */ static int json_object_deep_copy_recursive(struct json_object *src, struct json_object *parent, - const char *key_in_parent, size_t index_in_parent, - struct json_object **dst, + const struct lh_string *key_in_parent, + size_t index_in_parent, struct json_object **dst, json_c_shallow_copy_fn *shallow_copy) { struct json_object_iter iter; @@ -1761,7 +1804,7 @@ static int json_object_deep_copy_recursive(struct json_object *src, struct json_ return -1; } - if (json_object_object_add(*dst, iter.key, jso) < 0) + if (json_object_object_add_internal(*dst, iter.key, jso, 0) < 0) { json_object_put(jso); return -1; diff --git a/json_object.h b/json_object.h index e3d4d56..6cf2594 100644 --- a/json_object.h +++ b/json_object.h @@ -547,6 +547,19 @@ JSON_EXPORT json_bool json_object_object_get_ex_len(const struct json_object *ob */ JSON_EXPORT void json_object_object_del(struct json_object *obj, const char *key); +/** Delete the given json_object field + * + * The reference count will be decremented for the deleted object. If there + * are no more owners of the value represented by this key, then the value is + * freed. Otherwise, the reference to the value will remain in memory. + * + * @param obj the json_object instance + * @param key the object field name, + * which is not terminated by a NULL ( @c '\0' ) character + * @param len the length of @p key + */ +void json_object_object_del_len(struct json_object *jso, const char *key, const int len); + /** * Iterate through all keys and values of an object. * @@ -560,17 +573,18 @@ JSON_EXPORT void json_object_object_del(struct json_object *obj, const char *key * @param val the local name for the json_object* object variable defined in * the body */ -#if defined(__GNUC__) && !defined(__STRICT_ANSI__) && (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) && \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) #define json_object_object_foreach(obj, key, val) \ - char *key = NULL; \ + struct lh_string *key = NULL; \ struct json_object *val __attribute__((__unused__)) = NULL; \ for (struct lh_entry *entry##key = json_object_get_object(obj)->head, \ *entry_next##key = NULL; \ ({ \ if (entry##key) \ { \ - key = (char *)lh_entry_k(entry##key); \ + key = (struct lh_string *)lh_entry_k(entry##key); \ val = (struct json_object *)lh_entry_v(entry##key); \ entry_next##key = entry##key->next; \ }; \ @@ -600,7 +614,7 @@ JSON_EXPORT void json_object_object_del(struct json_object *obj, const char *key */ #define json_object_object_foreachC(obj, iter) \ for (iter.entry = json_object_get_object(obj)->head; \ - (iter.entry ? (iter.key = (char *)lh_entry_k(iter.entry), \ + (iter.entry ? (iter.key = (struct lh_string *)lh_entry_k(iter.entry), \ iter.val = (struct json_object *)lh_entry_v(iter.entry), iter.entry) \ : 0); \ iter.entry = iter.entry->next) @@ -746,7 +760,7 @@ JSON_EXPORT struct json_object *json_object_new_boolean(json_bool b); * The type is coerced to a json_bool if the passed object is not a json_bool. * integer and double objects will return 0 if there value is zero * or 1 otherwise. If the passed object is a string it will return - * 1 if it has a non zero length. + * 1 if it has a non zero length. * If any other object type is passed 0 will be returned, even non-empty * json_type_array and json_type_object objects. * @@ -1114,8 +1128,8 @@ JSON_EXPORT int json_object_equal(struct json_object *obj1, struct json_object * * * @return On success 1 or 2, -1 on errors */ -typedef int(json_c_shallow_copy_fn)(json_object *src, json_object *parent, const char *key, - size_t index, json_object **dst); +typedef int(json_c_shallow_copy_fn)(json_object *src, json_object *parent, + const struct lh_string *key, size_t index, json_object **dst); /** * The default shallow copy implementation for use with json_object_deep_copy(). diff --git a/json_object_iterator.c b/json_object_iterator.c index 1c2b3f2..0f065a4 100644 --- a/json_object_iterator.c +++ b/json_object_iterator.c @@ -104,12 +104,12 @@ void json_object_iter_next(struct json_object_iterator *iter) /** * **************************************************************************** */ -const char *json_object_iter_peek_name(const struct json_object_iterator *iter) +const struct lh_string *json_object_iter_peek_name(const struct json_object_iterator *iter) { JASSERT(NULL != iter); JASSERT(kObjectEndIterValue != iter->opaque_); - return (const char *)(((const struct lh_entry *)iter->opaque_)->k); + return (const struct lh_string *)(((const struct lh_entry *)iter->opaque_)->k); } /** diff --git a/json_object_iterator.h b/json_object_iterator.h index a9b1433..8b56479 100644 --- a/json_object_iterator.h +++ b/json_object_iterator.h @@ -168,7 +168,8 @@ JSON_EXPORT void json_object_iter_next(struct json_object_iterator *iter); * deleted or modified, and MUST NOT be modified or * freed by the user. */ -JSON_EXPORT const char *json_object_iter_peek_name(const struct json_object_iterator *iter); +JSON_EXPORT const struct lh_string * +json_object_iter_peek_name(const struct json_object_iterator *iter); /** Returns a pointer to the json-c instance representing the * value of the referenced name/value pair, without altering diff --git a/json_types.h b/json_types.h index b7e55ad..2892ef3 100644 --- a/json_types.h +++ b/json_types.h @@ -33,7 +33,7 @@ struct printbuf; */ struct json_object_iter { - char *key; + struct lh_string *key; struct json_object *val; struct lh_entry *entry; }; diff --git a/json_visit.c b/json_visit.c index fb16fa6..43f2ec0 100644 --- a/json_visit.c +++ b/json_visit.c @@ -13,7 +13,7 @@ #include "json_visit.h" #include "linkhash.h" -static int _json_c_visit(json_object *jso, json_object *parent_jso, const char *jso_key, +static int _json_c_visit(json_object *jso, json_object *parent_jso, const struct lh_string *jso_key, size_t *jso_index, json_c_visit_userfunc *userfunc, void *userarg); int json_c_visit(json_object *jso, int future_flags, json_c_visit_userfunc *userfunc, void *userarg) @@ -28,7 +28,7 @@ int json_c_visit(json_object *jso, int future_flags, json_c_visit_userfunc *user default: return JSON_C_VISIT_RETURN_ERROR; } } -static int _json_c_visit(json_object *jso, json_object *parent_jso, const char *jso_key, +static int _json_c_visit(json_object *jso, json_object *parent_jso, const struct lh_string *jso_key, size_t *jso_index, json_c_visit_userfunc *userfunc, void *userarg) { int userret = userfunc(jso, 0, parent_jso, jso_key, jso_index, userarg); diff --git a/json_visit.h b/json_visit.h index 35c46f5..21f0013 100644 --- a/json_visit.h +++ b/json_visit.h @@ -13,7 +13,8 @@ extern "C" { #endif typedef int(json_c_visit_userfunc)(json_object *jso, int flags, json_object *parent_jso, - const char *jso_key, size_t *jso_index, void *userarg); + const struct lh_string *jso_key, size_t *jso_index, + void *userarg); /** * Visit each object in the JSON hierarchy starting at jso. diff --git a/linkhash.c b/linkhash.c index 36dd014..bc4c155 100644 --- a/linkhash.c +++ b/linkhash.c @@ -506,6 +506,11 @@ size_t lh_string_size(const struct lh_string *str) return (str->length > 0) ? (size_t)str->length : (size_t)(-(str->length)); } +size_t lh_string_print(const struct lh_string *key, FILE *stream) +{ + return fwrite(lh_string_data(key), lh_string_size(key), 1, stream); +} + const struct lh_string *lh_string_new_ptr(const size_t length, const char *data) { struct lh_string *result = malloc(sizeof(struct lh_string)); @@ -535,7 +540,7 @@ const struct lh_string *lh_string_new_imm(const size_t length, const char *data) result->length = -length; char *unconst = _LH_UNCONST(result->str.idata); memcpy(unconst, data, length); - unconst = '\0'; + unconst[length] = '\0'; return result; } diff --git a/linkhash.h b/linkhash.h index e304117..045474c 100644 --- a/linkhash.h +++ b/linkhash.h @@ -20,6 +20,7 @@ #define _json_c_linkhash_h_ #include "json_object.h" +#include #ifdef __cplusplus extern "C" { @@ -206,6 +207,14 @@ extern const char *lh_string_data(const struct lh_string *str); */ extern size_t lh_string_size(const struct lh_string *str); +/** + * @brief Print a `struct lh_string` to a given stream + * + * @param str value to print + * @param stream Stream to write data to + */ +extern size_t lh_string_print(const struct lh_string *str, FILE *stream); + /** * @brief Creates a new `struct lh_string` using the `pdata` field. * From afade9038a929a137625d9731992879ad5ab7a9f Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Mon, 5 Jul 2021 17:14:31 -0800 Subject: [PATCH 04/11] Edits to tests to reflect changes to API --- tests/test1.c | 4 ++- tests/testReplaceExisting.c | 26 +++++++++++-------- tests/test_deep_copy.c | 6 ++--- tests/test_null_keys_add.c | 43 ++++++++++++++++++++++++++----- tests/test_null_keys_add.expected | 2 ++ tests/test_object_iterator.c | 3 ++- tests/test_parse.c | 2 +- tests/test_visit.c | 38 +++++++++++++-------------- 8 files changed, 82 insertions(+), 42 deletions(-) diff --git a/tests/test1.c b/tests/test1.c index 4d29601..aa156f3 100644 --- a/tests/test1.c +++ b/tests/test1.c @@ -306,7 +306,9 @@ int main(int argc, char **argv) printf("my_object=\n"); json_object_object_foreach(my_object, key, val) { - printf("\t%s: %s\n", key, json_object_to_json_string(val)); + putchar('\t'); + lh_string_print(key, stdout); + printf(": %s\n", json_object_to_json_string(val)); } printf("my_object.to_string()=%s\n", json_object_to_json_string(my_object)); diff --git a/tests/testReplaceExisting.c b/tests/testReplaceExisting.c index ee3cae0..5d99212 100644 --- a/tests/testReplaceExisting.c +++ b/tests/testReplaceExisting.c @@ -24,10 +24,11 @@ int main(int argc, char **argv) int orig_count = 0; json_object_object_foreach(my_object, key0, val0) { - printf("Key at index %d is [%s] %d", orig_count, key0, (val0 == NULL)); - if (strcmp(key0, "deleteme") == 0) + printf("Key at index %d is [%s] %d", orig_count, lh_string_data(key0), + (val0 == NULL)); + if (strcmp(lh_string_data(key0), "deleteme") == 0) { - json_object_object_del(my_object, key0); + json_object_object_del(my_object, lh_string_data(key0)); printf(" (deleted)\n"); } else @@ -37,17 +38,19 @@ int main(int argc, char **argv) printf("==== replace-value first loop starting ====\n"); - const char *original_key = NULL; + const struct lh_string *original_key = NULL; orig_count = 0; json_object_object_foreach(my_object, key, val) { - printf("Key at index %d is [%s] %d\n", orig_count, key, (val == NULL)); + printf("Key at index %d is [%s] %d\n", orig_count, lh_string_data(key), + (val == NULL)); orig_count++; - if (strcmp(key, "foo2") != 0) + if (strcmp(lh_string_data(key), "foo2") != 0) continue; - printf("replacing value for key [%s]\n", key); + printf("replacing value for key [%s]\n", lh_string_data(key)); original_key = key; - json_object_object_add(my_object, key, json_object_new_string("zzz")); + json_object_object_add(my_object, lh_string_data(key0), + json_object_new_string("zzz")); } printf("==== second loop starting ====\n"); @@ -56,11 +59,12 @@ int main(int argc, char **argv) int retval = 0; json_object_object_foreach(my_object, key2, val2) { - printf("Key at index %d is [%s] %d\n", new_count, key2, (val2 == NULL)); + printf("Key at index %d is [%s] %d\n", new_count, lh_string_data(key2), + (val2 == NULL)); new_count++; - if (strcmp(key2, "foo2") != 0) + if (strcmp(lh_string_data(key2), "foo2") != 0) continue; - printf("pointer for key [%s] does %smatch\n", key2, + printf("pointer for key [%s] does %smatch\n", lh_string_data(key2), (key2 == original_key) ? "" : "NOT "); if (key2 != original_key) retval = 1; diff --git a/tests/test_deep_copy.c b/tests/test_deep_copy.c index 34ef1fe..b1c685e 100644 --- a/tests/test_deep_copy.c +++ b/tests/test_deep_copy.c @@ -93,14 +93,14 @@ int my_custom_serializer(struct json_object *jso, struct printbuf *pb, int level } json_c_shallow_copy_fn my_shallow_copy; -int my_shallow_copy(json_object *src, json_object *parent, const char *key, size_t index, - json_object **dst) +int my_shallow_copy(json_object *src, json_object *parent, const struct lh_string *key, + size_t index, json_object **dst) { int rc; rc = json_c_shallow_copy_default(src, parent, key, index, dst); if (rc < 0) return rc; - if (key != NULL && strcmp(key, "with_serializer") == 0) + if (key != NULL && strcmp(lh_string_data(key), "with_serializer") == 0) { printf("CALLED: my_shallow_copy on with_serializer object\n"); void *userdata = json_object_get_userdata(src); diff --git a/tests/test_null_keys_add.c b/tests/test_null_keys_add.c index ce31582..a2965cd 100644 --- a/tests/test_null_keys_add.c +++ b/tests/test_null_keys_add.c @@ -9,6 +9,7 @@ #include "json_inttypes.h" #include "json_object.h" #include "json_tokener.h" +#include "linkhash.h" int main(void) { @@ -65,10 +66,33 @@ int main(void) return 1; } + // Check the previous keys are still the same present + struct json_object *foo = json_object_object_get(parsed, foo_key); + if (!json_object_is_type(foo, json_type_double)) + { + printf("Key \"%s\" should be `json_type_double` (%d) but was %d (error!)\n", + foo_key, (int)json_type_double, json_object_get_type(foo)); + return 1; + } + else + { + printf("Key \"%s\" parsed as right type\n", foo_key); + } + struct json_object *bar = json_object_object_get(parsed, bar_key); + if (!json_object_is_type(bar, json_type_array)) + { + printf("Key \"%s\" should be `json_type_array` (%d) but was %d (error!)\n", bar_key, + (int)json_type_array, json_object_get_type(bar)); + return 1; + } + else + { + printf("Key \"%s\" parsed as right type\n", bar_key); + } + // Add the new key struct json_object *new_str = json_object_new_string_len(toadd_value, toadd_value_len); - if (json_object_object_add_ex(parsed, toadd_key, new_str, - JSON_C_OBJECT_ADD_KEY_IS_NEW | + if (json_object_object_add_ex_len(parsed, toadd_key, toadd_key_len, new_str, JSON_C_OBJECT_KEY_IS_CONSTANT) != 0) { printf("An error occured adding the key \"%s\" (error!)\n", toadd_key_printable); @@ -86,6 +110,13 @@ int main(void) { printf("Have three keys, but don't have the right value in \"%s\" (error!)\n", toadd_key_printable); + printf("Keys :\n"); + json_object_object_foreach(parsed, key, val) + { + putchar('\"'); + fwrite(lh_string_data(key), lh_string_size(key), 1, stdout); + printf("\" (%zd)\n", lh_string_size(key)); + } return 1; } else @@ -94,7 +125,7 @@ int main(void) } // Check the previous keys are still the same present - struct json_object *foo = json_object_object_get(parsed, foo_key); + foo = json_object_object_get(parsed, foo_key); if (!json_object_is_type(foo, json_type_double)) { printf("Key \"%s\" should be `json_type_double` (%d) but was %d (error!)\n", @@ -105,11 +136,11 @@ int main(void) { printf("Key \"%s\" is still the same\n", foo_key); } - struct json_object *bar = json_object_object_get(parsed, bar_key); - if (!json_object_is_type(foo, json_type_array)) + bar = json_object_object_get(parsed, bar_key); + if (!json_object_is_type(bar, json_type_array)) { printf("Key \"%s\" should be `json_type_array` (%d) but was %d (error!)\n", bar_key, - (int)json_type_array, json_object_get_type(foo)); + (int)json_type_array, json_object_get_type(bar)); return 1; } else diff --git a/tests/test_null_keys_add.expected b/tests/test_null_keys_add.expected index 5e9a9c1..621a6df 100644 --- a/tests/test_null_keys_add.expected +++ b/tests/test_null_keys_add.expected @@ -1,5 +1,7 @@ Parsed input: { "foo": 14.5, "bar": [] } Result is `json_type_object` +Key "foo" parsed as right type +Key "bar" parsed as right type Added the key "foo\0bar" successfully Key "foo" is still the same Key "bar" is still the same diff --git a/tests/test_object_iterator.c b/tests/test_object_iterator.c index da5192a..9e0a178 100644 --- a/tests/test_object_iterator.c +++ b/tests/test_object_iterator.c @@ -6,6 +6,7 @@ #include "json_object.h" #include "json_object_iterator.h" #include "json_tokener.h" +#include "linkhash.h" int main(int atgc, char **argv) { @@ -30,7 +31,7 @@ int main(int atgc, char **argv) while (!json_object_iter_equal(&it, &itEnd)) { - printf("%s\n", json_object_iter_peek_name(&it)); + printf("%s\n", lh_string_data(json_object_iter_peek_name(&it))); printf("%s\n", json_object_to_json_string(json_object_iter_peek_value(&it))); json_object_iter_next(&it); } diff --git a/tests/test_parse.c b/tests/test_parse.c index 5363f32..19238c4 100644 --- a/tests/test_parse.c +++ b/tests/test_parse.c @@ -215,7 +215,7 @@ static void do_clear_serializer(json_object *jso) } static int clear_serializer(json_object *jso, int flags, json_object *parent_jso, - const char *jso_key, size_t *jso_index, void *userarg) + const struct lh_string *jso_key, size_t *jso_index, void *userarg) { if (jso) json_object_set_serializer(jso, NULL, NULL, NULL); diff --git a/tests/test_visit.c b/tests/test_visit.c index eee469e..b226d87 100644 --- a/tests/test_visit.c +++ b/tests/test_visit.c @@ -68,17 +68,17 @@ int main(void) return 0; } -static int emit_object(json_object *jso, int flags, json_object *parent_jso, const char *jso_key, - size_t *jso_index, void *userarg) +static int emit_object(json_object *jso, int flags, json_object *parent_jso, + const struct lh_string *jso_key, size_t *jso_index, void *userarg) { printf("flags: 0x%x, key: %s, index: %ld, value: %s\n", flags, - (jso_key ? jso_key : "(null)"), (jso_index ? (long)*jso_index : -1L), + (jso_key ? lh_string_data(jso_key) : "(null)"), (jso_index ? (long)*jso_index : -1L), json_object_to_json_string(jso)); return JSON_C_VISIT_RETURN_CONTINUE; } -static int skip_arrays(json_object *jso, int flags, json_object *parent_jso, const char *jso_key, - size_t *jso_index, void *userarg) +static int skip_arrays(json_object *jso, int flags, json_object *parent_jso, + const struct lh_string *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); if (json_object_get_type(jso) == json_type_array) @@ -86,16 +86,16 @@ static int skip_arrays(json_object *jso, int flags, json_object *parent_jso, con return JSON_C_VISIT_RETURN_CONTINUE; } -static int pop_and_stop(json_object *jso, int flags, json_object *parent_jso, const char *jso_key, - size_t *jso_index, void *userarg) +static int pop_and_stop(json_object *jso, int flags, json_object *parent_jso, + const struct lh_string *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); - if (jso_key != NULL && strcmp(jso_key, "subobj1") == 0) + if (jso_key != NULL && strcmp(lh_string_data(jso_key), "subobj1") == 0) { printf("POP after handling subobj1\n"); return JSON_C_VISIT_RETURN_POP; } - if (jso_key != NULL && strcmp(jso_key, "obj3") == 0) + if (jso_key != NULL && strcmp(lh_string_data(jso_key), "obj3") == 0) { printf("STOP after handling obj3\n"); return JSON_C_VISIT_RETURN_STOP; @@ -103,11 +103,11 @@ static int pop_and_stop(json_object *jso, int flags, json_object *parent_jso, co return JSON_C_VISIT_RETURN_CONTINUE; } -static int err_on_subobj2(json_object *jso, int flags, json_object *parent_jso, const char *jso_key, - size_t *jso_index, void *userarg) +static int err_on_subobj2(json_object *jso, int flags, json_object *parent_jso, + const struct lh_string *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); - if (jso_key != NULL && strcmp(jso_key, "subobj2") == 0) + if (jso_key != NULL && strcmp(lh_string_data(jso_key), "subobj2") == 0) { printf("ERROR after handling subobj1\n"); return JSON_C_VISIT_RETURN_ERROR; @@ -115,8 +115,8 @@ static int err_on_subobj2(json_object *jso, int flags, json_object *parent_jso, return JSON_C_VISIT_RETURN_CONTINUE; } -static int pop_array(json_object *jso, int flags, json_object *parent_jso, const char *jso_key, - size_t *jso_index, void *userarg) +static int pop_array(json_object *jso, int flags, json_object *parent_jso, + const struct lh_string *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); if (jso_index != NULL && (*jso_index == 0)) @@ -127,8 +127,8 @@ static int pop_array(json_object *jso, int flags, json_object *parent_jso, const return JSON_C_VISIT_RETURN_CONTINUE; } -static int stop_array(json_object *jso, int flags, json_object *parent_jso, const char *jso_key, - size_t *jso_index, void *userarg) +static int stop_array(json_object *jso, int flags, json_object *parent_jso, + const struct lh_string *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); if (jso_index != NULL && (*jso_index == 0)) @@ -139,11 +139,11 @@ static int stop_array(json_object *jso, int flags, json_object *parent_jso, cons return JSON_C_VISIT_RETURN_CONTINUE; } -static int err_return(json_object *jso, int flags, json_object *parent_jso, const char *jso_key, - size_t *jso_index, void *userarg) +static int err_return(json_object *jso, int flags, json_object *parent_jso, + const struct lh_string *jso_key, size_t *jso_index, void *userarg) { printf("flags: 0x%x, key: %s, index: %ld, value: %s\n", flags, - (jso_key ? jso_key : "(null)"), (jso_index ? (long)*jso_index : -1L), + (jso_key ? lh_string_data(jso_key) : "(null)"), (jso_index ? (long)*jso_index : -1L), json_object_to_json_string(jso)); return 100; } From c9ab2f338e86e99de177caa560c4c6815eaae790 Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Mon, 5 Jul 2021 19:50:42 -0800 Subject: [PATCH 05/11] Changes to tokener to allow null characters `struct lh_string` really needs to be renamed and moved to json_object.h --- json_object.c | 2 -- json_object_private.h | 16 ++++++++++++++++ json_tokener.c | 6 ++++-- json_tokener.h | 7 ++++--- linkhash.c | 4 ++-- linkhash.h | 4 ++-- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/json_object.c b/json_object.c index cae67dd..e9fffab 100644 --- a/json_object.c +++ b/json_object.c @@ -116,8 +116,6 @@ static inline const struct json_object_string *JC_STRING_C(const struct json_obj static inline struct json_object *json_object_new(enum json_type o_type, size_t alloc_size, json_object_to_json_string_fn *to_json_string); -static int json_object_object_add_internal(struct json_object *jso, const struct lh_string *key, - struct json_object *const val, const unsigned opts); static int json_object_object_del_internal(struct json_object *jso_base, const struct lh_string *key); static json_bool json_object_object_get_internal(const struct json_object *jso, diff --git a/json_object_private.h b/json_object_private.h index 4f5e7c4..5b08dd1 100644 --- a/json_object_private.h +++ b/json_object_private.h @@ -98,6 +98,22 @@ struct json_object_string void _json_c_set_last_err(const char *err_fmt, ...); +/** + * @brief Add an object field to a json_object of type json_type_object + * + * The semantics are identical to json_object_object_add_ex, except that @p key + * contains both the data and the length. + * + * @param obj the json_object instance + * @param key the object field name (a private copy will be duplicated) + * @param val a json_object or NULL member to associate with the given field + * @param opts process-modifying options. To specify multiple options, use + * (OPT1|OPT2) + * @return On success, @c 0 is returned. + * On error, a negative value is returned. + */ +int json_object_object_add_internal(struct json_object *obj, const struct lh_string *key, + struct json_object *const val, const unsigned opts); /** * The characters that can make up hexadecimal numbers */ diff --git a/json_tokener.c b/json_tokener.c index aad463a..8fea883 100644 --- a/json_tokener.c +++ b/json_tokener.c @@ -1105,7 +1105,9 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * { printbuf_memappend_fast(tok->pb, case_start, str - case_start); - obj_field_name = strdup(tok->pb->buf); + obj_field_name = + lh_string_new_imm(tok->pb->bpos, tok->pb->buf); + // lh_string_print(obj_field_name, stdout); saved_state = json_tokener_state_object_field_end; state = json_tokener_state_eatws; break; @@ -1153,7 +1155,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * goto redo_char; case json_tokener_state_object_value_add: - json_object_object_add(current, obj_field_name, obj); + json_object_object_add_internal(current, obj_field_name, obj, 0); free(obj_field_name); obj_field_name = NULL; saved_state = json_tokener_state_object_sep; diff --git a/json_tokener.h b/json_tokener.h index a07e12c..9a0d411 100644 --- a/json_tokener.h +++ b/json_tokener.h @@ -17,6 +17,7 @@ #define _json_tokener_h_ #include "json_object.h" +#include "linkhash.h" #include #ifdef __cplusplus @@ -85,7 +86,7 @@ struct json_tokener_srec enum json_tokener_state state, saved_state; struct json_object *obj; struct json_object *current; - char *obj_field_name; + struct lh_string *obj_field_name; }; #define JSON_TOKENER_DEFAULT_DEPTH 32 @@ -215,7 +216,7 @@ JSON_EXPORT struct json_tokener *json_tokener_new_ex(int depth); JSON_EXPORT void json_tokener_free(struct json_tokener *tok); /** - * Reset the state of a json_tokener, to prepare to parse a + * Reset the state of a json_tokener, to prepare to parse a * brand new JSON object. */ JSON_EXPORT void json_tokener_reset(struct json_tokener *tok); @@ -279,7 +280,7 @@ JSON_EXPORT void json_tokener_set_flags(struct json_tokener *tok, int flags); * the length of the last len parameter passed in. * * The tokener does \b not maintain an internal buffer so the caller is - * responsible for a subsequent call to json_tokener_parse_ex with an + * responsible for a subsequent call to json_tokener_parse_ex with an * appropriate str parameter starting with the extra characters. * * This interface is presently not 64-bit clean due to the int len argument diff --git a/linkhash.c b/linkhash.c index bc4c155..4bb9844 100644 --- a/linkhash.c +++ b/linkhash.c @@ -511,7 +511,7 @@ size_t lh_string_print(const struct lh_string *key, FILE *stream) return fwrite(lh_string_data(key), lh_string_size(key), 1, stream); } -const struct lh_string *lh_string_new_ptr(const size_t length, const char *data) +struct lh_string *lh_string_new_ptr(const size_t length, const char *data) { struct lh_string *result = malloc(sizeof(struct lh_string)); if (result == NULL) @@ -523,7 +523,7 @@ const struct lh_string *lh_string_new_ptr(const size_t length, const char *data) return result; } -const struct lh_string *lh_string_new_imm(const size_t length, const char *data) +struct lh_string *lh_string_new_imm(const size_t length, const char *data) { struct lh_string *result; if (length > diff --git a/linkhash.h b/linkhash.h index 045474c..a8b82f2 100644 --- a/linkhash.h +++ b/linkhash.h @@ -225,7 +225,7 @@ extern size_t lh_string_print(const struct lh_string *str, FILE *stream); * @param data The data to include * @return `NULL` on error */ -extern const struct lh_string *lh_string_new_ptr(const size_t length, const char *data); +extern struct lh_string *lh_string_new_ptr(const size_t length, const char *data); /** * @brief Creates a new `struct lh_string` using the `idata` field. @@ -234,7 +234,7 @@ extern const struct lh_string *lh_string_new_ptr(const size_t length, const char * @param data The data to include and copy into the returned value * @return `NULL` on error */ -extern const struct lh_string *lh_string_new_imm(const size_t length, const char *data); +extern struct lh_string *lh_string_new_imm(const size_t length, const char *data); /** * Create a new linkhash table. From 0a8f719eda9b0e7cae67be0f7e6b7598074d9f73 Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Tue, 6 Jul 2021 20:58:33 -0800 Subject: [PATCH 06/11] Add additional tests and fix `test_null_keys_get` --- tests/CMakeLists.txt | 2 + tests/test_null_keys_del.c | 100 ++++++++++++++++++++++++++++ tests/test_null_keys_del.expected | 5 ++ tests/test_null_keys_del.test | 1 + tests/test_null_keys_get.c | 2 +- tests/test_null_keys_print.c | 18 +++++ tests/test_null_keys_print.expected | 1 + tests/test_null_keys_print.test | 1 + 8 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 tests/test_null_keys_del.c create mode 100644 tests/test_null_keys_del.expected create mode 120000 tests/test_null_keys_del.test create mode 100644 tests/test_null_keys_print.c create mode 100644 tests/test_null_keys_print.expected create mode 120000 tests/test_null_keys_print.test diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7d84442..e339602 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,8 @@ set(ALL_TEST_NAMES test_null test_null_keys_add test_null_keys_get + test_null_keys_del + test_null_keys_print test_parse test_parse_int64 test_printbuf diff --git a/tests/test_null_keys_del.c b/tests/test_null_keys_del.c new file mode 100644 index 0000000..a2f9c34 --- /dev/null +++ b/tests/test_null_keys_del.c @@ -0,0 +1,100 @@ +/* + * Tests if binary strings are supported. + */ + +#include "config.h" +#include +#include + +#include "json_inttypes.h" +#include "json_object.h" +#include "json_tokener.h" + +int main(void) +{ + /* this test has embedded null characters in the key and value. + * check that it's still included after deserializing. */ + const char *input = "{ \"biff\\u0000\": null, \"\\u0000zxcvbnm\": 178 }"; + const char *expected_biff = "biff\0"; + const int expected_biff_len = 5; + const char *expected_biff_printable = "biff\\u0000"; + const char *expected_zxcv = "\0zxcvbnm"; + const int expected_zxcv_len = 8; + const char *expected_zxcv_printable = "\\u0000zxcvbnm"; + + struct json_object *parsed = json_tokener_parse(input); + struct json_object *val; + json_bool key_present; + + printf("Parsed input: %s\n", input); + printf("Result is "); + if (parsed == NULL) + { + printf("NULL (error!)\n"); + return 1; // Error somewhere + } + else if (!json_object_is_type(parsed, json_type_object)) + { + printf("not `json_type_object` (error!)\n"); + return 1; // Error somewhere + } + else + { + printf("`json_type_object`\n"); + } + + // Check nothing odd happened in parsing + if (json_object_object_length(parsed) != 2) + { + printf("Should contain two fields (has %d) (error!)", + json_object_object_length(parsed)); + return 1; + } + + key_present = json_object_object_get_ex_len(parsed, expected_biff, expected_biff_len, &val); + if (!key_present || !json_object_is_type(val, json_type_null)) + { + printf("The key \"%s\" should be present and should be NULL (error!)\n", + expected_biff_printable); + return 1; + } + key_present = json_object_object_get_ex_len(parsed, expected_zxcv, expected_zxcv_len, &val); + if (!key_present || !json_object_is_type(val, json_type_int) || + json_object_get_int(val) != 178) + { + printf("The key \"%s\" should be present and should be 178 (error!)\n", + expected_zxcv_printable); + return 1; + } + printf("Expected keys (\"%s\" and \"%s\") present with expected values\n", + expected_biff_printable, expected_zxcv_printable); + + // Delete one key + json_object_object_del_len(parsed, expected_zxcv, expected_zxcv_len); + + // Check it is gone + if (json_object_object_length(parsed) != 1) + { + printf("Should contain only one field (has %d) (error!)", + json_object_object_length(parsed)); + return 1; + } + key_present = json_object_object_get_ex_len(parsed, expected_biff, expected_biff_len, &val); + if (!key_present || !json_object_is_type(val, json_type_null)) + { + printf("The key \"%s\" should be present and should be NULL (error!)\n", + expected_biff_printable); + return 1; + } + key_present = json_object_object_get_ex_len(parsed, expected_zxcv, expected_zxcv_len, &val); + if (key_present) + { + printf("The key \"%s\" should not be present (error!)\n", expected_zxcv_printable); + return 1; + } + printf("Key deleted properly\n"); + + json_object_put(parsed); + printf("PASS\n"); + return 0; +} diff --git a/tests/test_null_keys_del.expected b/tests/test_null_keys_del.expected new file mode 100644 index 0000000..44f1682 --- /dev/null +++ b/tests/test_null_keys_del.expected @@ -0,0 +1,5 @@ +Parsed input: { "biff\u0000": null, "\u0000zxcvbnm": 178 } +Result is `json_type_object` +Expected keys ("biff\0" and "\0zxcvbnm") present with expected values +Key deleted properly +PASS diff --git a/tests/test_null_keys_del.test b/tests/test_null_keys_del.test new file mode 120000 index 0000000..58a13f4 --- /dev/null +++ b/tests/test_null_keys_del.test @@ -0,0 +1 @@ +test_basic.test \ No newline at end of file diff --git a/tests/test_null_keys_get.c b/tests/test_null_keys_get.c index d63f579..b0c7d26 100644 --- a/tests/test_null_keys_get.c +++ b/tests/test_null_keys_get.c @@ -19,7 +19,7 @@ int main(void) const int expected_key_len = 7; const char *expected_key_printable = "foo\\0bar"; const char *expected_value = "qwerty\0asdf"; - const int expected_value_len = 12; + const int expected_value_len = 11; const char *expected_value_printable = "qwerty\\0asdf"; struct json_object *parsed = json_tokener_parse(input); diff --git a/tests/test_null_keys_print.c b/tests/test_null_keys_print.c new file mode 100644 index 0000000..03b74c8 --- /dev/null +++ b/tests/test_null_keys_print.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +#include "json.h" +#include "parse_flags.h" + +int main(int argc, char **argv) +{ + struct json_object *new_obj; + + new_obj = json_tokener_parse("{ \"foo\\u0000bar\": \"qwerty\\u0000asdf\" }"); + printf("new_obj.to_string()=%s\n", json_object_to_json_string(new_obj)); + json_object_put(new_obj); + + return EXIT_SUCCESS; +} diff --git a/tests/test_null_keys_print.expected b/tests/test_null_keys_print.expected new file mode 100644 index 0000000..0af8163 --- /dev/null +++ b/tests/test_null_keys_print.expected @@ -0,0 +1 @@ +new_obj.to_string()={ "foo\u0000bar": "qwerty\u0000asdf" } diff --git a/tests/test_null_keys_print.test b/tests/test_null_keys_print.test new file mode 120000 index 0000000..58a13f4 --- /dev/null +++ b/tests/test_null_keys_print.test @@ -0,0 +1 @@ +test_basic.test \ No newline at end of file From 2c9019f7ff4f80d3d164ee91f891dbf4fde0bfbd Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Sat, 10 Jul 2021 23:19:31 -0800 Subject: [PATCH 07/11] Rename lh_string to json_key and move to json_object.h --- json-c.sym | 8 +- json_object.c | 95 +++++++++++++------- json_object.h | 138 ++++++++++++++++++++++++++---- json_object_iterator.c | 4 +- json_object_iterator.h | 2 +- json_object_private.h | 58 +++++++++---- json_tokener.c | 5 +- json_tokener.h | 3 +- json_types.h | 4 +- json_visit.c | 4 +- json_visit.h | 2 +- linkhash.c | 56 +----------- linkhash.h | 72 ---------------- tests/test1.c | 2 +- tests/testReplaceExisting.c | 22 ++--- tests/test_deep_copy.c | 6 +- tests/test_null_keys_add.c | 4 +- tests/test_null_keys_del.expected | 2 +- tests/test_object_iterator.c | 2 +- tests/test_parse.c | 2 +- tests/test_visit.c | 24 +++--- 21 files changed, 284 insertions(+), 231 deletions(-) diff --git a/json-c.sym b/json-c.sym index e69e18b..2d6bd86 100644 --- a/json-c.sym +++ b/json-c.sym @@ -166,12 +166,14 @@ JSONC_0.15 { JSONC_0.16 { global: - lh_string_data; - lh_string_size; - lh_string_print; + json_key_data; + json_key_size; json_object_object_add_len; json_object_object_add_ex_len; + json_object_object_add_key; json_object_object_del_len; + json_object_object_del_key; json_object_object_get_len; json_object_object_get_ex_len; + json_object_object_get_key; } JSONC_0.15; diff --git a/json_object.c b/json_object.c index e9fffab..b48b889 100644 --- a/json_object.c +++ b/json_object.c @@ -116,12 +116,6 @@ static inline const struct json_object_string *JC_STRING_C(const struct json_obj static inline struct json_object *json_object_new(enum json_type o_type, size_t alloc_size, json_object_to_json_string_fn *to_json_string); -static int json_object_object_del_internal(struct json_object *jso_base, - const struct lh_string *key); -static json_bool json_object_object_get_internal(const struct json_object *jso, - const struct lh_string *key, - struct json_object **value); - static void json_object_object_delete(struct json_object *jso_base); static void json_object_string_delete(struct json_object *jso); static void json_object_array_delete(struct json_object *jso); @@ -512,7 +506,7 @@ static int json_object_object_to_json_string(struct json_object *jso, struct pri printbuf_strappend(pb, " "); indent(pb, level + 1, flags); printbuf_strappend(pb, "\""); - json_escape_str(pb, lh_string_data(iter.key), lh_string_size(iter.key), flags); + json_escape_str(pb, json_key_data(iter.key), json_key_size(iter.key), flags); if (flags & JSON_C_TO_STRING_SPACED) printbuf_strappend(pb, "\": "); else @@ -583,20 +577,20 @@ int json_object_object_add_ex(struct json_object *jso, const char *const key, int json_object_object_add_ex_len(struct json_object *jso, const char *const key, const int len, struct json_object *const val, const unsigned opts) { - // Created on the stack rather than calling `lh_string_new_ptr` - // or `lh_string_new_imm` since this saves copying `key` if it turns + // Created on the stack rather than calling `json_key_new_ptr` + // or `json_key_new_imm` since this saves copying `key` if it turns // out the value already exists in the hash table - const struct lh_string hashable = {.length = len, .str = {.pdata = key}}; - return json_object_object_add_internal(jso, &hashable, val, opts); + const struct json_key hashable = {.length = len, .str = {.pdata = key}}; + return json_object_object_add_key(jso, &hashable, val, opts); } -int json_object_object_add_internal(struct json_object *jso, const struct lh_string *key, - struct json_object *const val, const unsigned opts) +int json_object_object_add_key(struct json_object *jso, const struct json_key *key, + struct json_object *const val, const unsigned opts) { struct json_object *existing_value; struct lh_entry *existing_entry; unsigned long hash; - /** Required due to the `lh_get_hash` function wanting a `const struct lh_string *` */ + /** Required due to the `lh_get_hash` function wanting a `const struct json_key *` */ assert(json_object_get_type(jso) == json_type_object); @@ -619,12 +613,12 @@ int json_object_object_add_internal(struct json_object *jso, const struct lh_str { // need to copy `key` because the caller might have created it // on the stack, which would be more efficient than copying - // `lh_string_data` into `key->string.idata` if it had happened + // `json_key_data` into `key->string.idata` if it had happened // that `existing_entry` wasn't NULL. - const struct lh_string *k = + const struct json_key *k = (opts & JSON_C_OBJECT_KEY_IS_CONSTANT) - ? lh_string_new_ptr(lh_string_size(key), lh_string_data(key)) - : lh_string_new_imm(lh_string_size(key), lh_string_data(key)); + ? json_key_new_ptr(json_key_size(key), json_key_data(key)) + : json_key_new_imm(json_key_size(key), json_key_data(key)); // TODO some optimization where (opts & JSON_C_OBJECT_ADD_KEY_IS_NEW) if (k == NULL) { @@ -632,7 +626,7 @@ int json_object_object_add_internal(struct json_object *jso, const struct lh_str } return lh_table_insert_w_hash(JC_OBJECT(jso)->c_object, k, val, hash, opts & ~JSON_C_OBJECT_KEY_IS_CONSTANT); - // `struct lh_string` always needs to be freed, + // `struct json_key` always needs to be freed, // so JSON_C_OBJECT_KEY_IS_CONSTANT cannot be set } else @@ -703,8 +697,8 @@ json_bool json_object_object_get_ex_len(const struct json_object *jso, const cha { case json_type_object: { - const struct lh_string hashable = {.length = len, .str = {.pdata = key}}; - return json_object_object_get_internal(jso, &hashable, value); + const struct json_key hashable = {.length = len, .str = {.pdata = key}}; + return json_object_object_get_key(jso, &hashable, value); } default: if (value != NULL) @@ -713,8 +707,8 @@ json_bool json_object_object_get_ex_len(const struct json_object *jso, const cha } } -json_bool json_object_object_get_internal(const struct json_object *jso, - const struct lh_string *key, struct json_object **value) +json_bool json_object_object_get_key(const struct json_object *jso, const struct json_key *key, + struct json_object **value) { assert(json_object_get_type(jso) == json_type_object); return lh_table_lookup_ex(JC_OBJECT_C(jso)->c_object, key, (void **)value); @@ -727,11 +721,11 @@ void json_object_object_del(struct json_object *jso, const char *key) void json_object_object_del_len(struct json_object *jso, const char *key, const int len) { - const struct lh_string hashable = {.length = len, .str = {.pdata = key}}; - json_object_object_del_internal(jso, &hashable); + const struct json_key hashable = {.length = len, .str = {.pdata = key}}; + json_object_object_del_key(jso, &hashable); } -int json_object_object_del_internal(struct json_object *jso, const struct lh_string *key) +int json_object_object_del_key(struct json_object *jso, const struct json_key *key) { assert(json_object_get_type(jso) == json_type_object); return lh_table_delete(JC_OBJECT(jso)->c_object, key); @@ -1721,7 +1715,7 @@ static int json_object_copy_serializer_data(struct json_object *src, struct json * * This always returns -1 or 1. It will never return 2 since it does not copy the serializer. */ -int json_c_shallow_copy_default(json_object *src, json_object *parent, const struct lh_string *key, +int json_c_shallow_copy_default(json_object *src, json_object *parent, const struct json_key *key, size_t index, json_object **dst) { switch (src->o_type) @@ -1769,7 +1763,7 @@ int json_c_shallow_copy_default(json_object *src, json_object *parent, const str * Note: caller is responsible for freeing *dst if this fails and returns -1. */ static int json_object_deep_copy_recursive(struct json_object *src, struct json_object *parent, - const struct lh_string *key_in_parent, + const struct json_key *key_in_parent, size_t index_in_parent, struct json_object **dst, json_c_shallow_copy_fn *shallow_copy) { @@ -1802,7 +1796,7 @@ static int json_object_deep_copy_recursive(struct json_object *src, struct json_ return -1; } - if (json_object_object_add_internal(*dst, iter.key, jso, 0) < 0) + if (json_object_object_add_key(*dst, iter.key, jso, 0) < 0) { json_object_put(jso); return -1; @@ -1876,3 +1870,46 @@ static void json_abort(const char *message) fprintf(stderr, "json-c aborts with error: %s\n", message); abort(); } + +size_t json_key_size(const struct json_key *str) +{ + return (str->length > 0) ? (size_t)str->length : (size_t)(-(str->length)); +} + +const char *json_key_data(const struct json_key *str) +{ + return (str->length > 0) ? str->str.pdata : str->str.idata; +} + +struct json_key *json_key_new_ptr(const size_t length, const char *data) +{ + struct json_key *result = malloc(sizeof(struct json_key)); + if (result == NULL) + { + return NULL; + } + result->length = length; + result->str.pdata = data; + return result; +} + +struct json_key *json_key_new_imm(const size_t length, const char *data) +{ + struct json_key *result; + if (length > + SSIZE_T_MAX - (sizeof(struct json_key) - sizeof(((struct json_key *)NULL)->str)) - 1) + { + return NULL; + } + result = + malloc(sizeof(struct json_key) - sizeof(((struct json_key *)NULL)->str) + length + 1); + if (result == NULL) + { + return NULL; + } + result->length = -length; + char *unconst = _LH_UNCONST(result->str.idata); + memcpy(unconst, data, length); + unconst[length] = '\0'; + return result; +} diff --git a/json_object.h b/json_object.h index 6cf2594..771ea73 100644 --- a/json_object.h +++ b/json_object.h @@ -456,8 +456,27 @@ JSON_EXPORT int json_object_object_add_ex_len(struct json_object *obj, const cha const int len, struct json_object *const val, const unsigned opts); -/** Get the json_object associate with a given object field. - * Deprecated/discouraged: used json_object_object_get_ex instead. +/** + * @brief Add an object field to a json_object of type json_type_object + * + * The semantics are identical to `json_object_object_add_ex`, except that @p key + * contains both the data and the length. + * + * @param obj the json_object instance + * @param key the object field name (a private copy will be duplicated) + * @param val a json_object or NULL member to associate with the given field + * @param opts process-modifying options. To specify multiple options, use + * (OPT1|OPT2) + * @return On success, @c 0 is returned. + * On error, a negative value is returned. + */ +JSON_EXPORT int json_object_object_add_key(struct json_object *obj, const struct json_key *key, + struct json_object *const val, const unsigned opts); + +/** + * @brief Get the json_object associate with a given object field. + * + * @deprecated Deprecated/discouraged: used json_object_object_get_ex instead. * * This functions exactly like calling * @code json_object_object_get_len(obj, key, strlen(key)) @endcode @@ -469,8 +488,10 @@ JSON_EXPORT int json_object_object_add_ex_len(struct json_object *obj, const cha */ JSON_EXPORT struct json_object *json_object_object_get(const struct json_object *obj, const char *key); -/** Get the json_object associate with a given object field. - * Deprecated/discouraged: used json_object_object_get_ex_len instead. +/** + * @brief Get the json_object associate with a given object field. + * + * @deprecated Deprecated/discouraged: used json_object_object_get_ex_len instead. * * This returns NULL if the field is found but its value is null, or if * the field is not found, or if obj is not a json_type_object. If you @@ -496,7 +517,8 @@ JSON_EXPORT struct json_object *json_object_object_get(const struct json_object JSON_EXPORT struct json_object *json_object_object_get_len(const struct json_object *obj, const char *key, const int len); -/** Get the json_object associated with a given object field. +/** + * @brief Get the json_object associated with a given object field. * * This functions exactly like calling * @code json_object_object_get_ex_len(obj, key, strlen(key), value) @endcode @@ -513,7 +535,8 @@ JSON_EXPORT struct json_object *json_object_object_get_len(const struct json_obj JSON_EXPORT json_bool json_object_object_get_ex(const struct json_object *obj, const char *key, struct json_object **value); -/** Get the json_object associated with a given object field. +/** + * @brief Get the json_object associated with a given object field. * * This returns true if the key is found, false in all other cases (including * if obj isn't a json_type_object). @@ -536,7 +559,29 @@ JSON_EXPORT json_bool json_object_object_get_ex(const struct json_object *obj, c JSON_EXPORT json_bool json_object_object_get_ex_len(const struct json_object *obj, const char *key, const int len, struct json_object **value); -/** Delete the given json_object field +/** + * @brief Get the json_object associated with a given object field. + * + * *No* reference counts will be changed. There is no need to manually adjust + * reference counts through the json_object_put/json_object_get methods unless + * you need to have the child (value) reference maintain a different lifetime + * than the owning parent (obj). Ownership of value is retained by obj. + * + * @param obj the json_object instance + * @param key the object field name + * @param value a pointer where to store a reference to the json_object + * associated with the given field name. + * \n + * It is safe to pass a NULL value. + * @returns true if the key is found, false in all other cases (including + * if obj isn't a json_type_object) + */ +JSON_EXPORT json_bool json_object_object_get_key(const struct json_object *jso, + const struct json_key *key, + struct json_object **value); + +/** + * @brief Delete the given json_object field * * The reference count will be decremented for the deleted object. If there * are no more owners of the value represented by this key, then the value is @@ -547,7 +592,8 @@ JSON_EXPORT json_bool json_object_object_get_ex_len(const struct json_object *ob */ JSON_EXPORT void json_object_object_del(struct json_object *obj, const char *key); -/** Delete the given json_object field +/** + * @brief Delete the given json_object field * * The reference count will be decremented for the deleted object. If there * are no more owners of the value represented by this key, then the value is @@ -558,10 +604,24 @@ JSON_EXPORT void json_object_object_del(struct json_object *obj, const char *key * which is not terminated by a NULL ( @c '\0' ) character * @param len the length of @p key */ -void json_object_object_del_len(struct json_object *jso, const char *key, const int len); +JSON_EXPORT void json_object_object_del_len(struct json_object *jso, const char *key, + const int len); /** - * Iterate through all keys and values of an object. + * @brief Delete the given json_object field + * + * The reference count will be decremented for the deleted object. If there + * are no more owners of the value represented by this key, then the value is + * freed. Otherwise, the reference to the value will remain in memory. + * + * @param obj the json_object instance + * @param key the object field name + */ +JSON_EXPORT int json_object_object_del_key(struct json_object *jso_base, + const struct json_key *key); + +/** + * @brief Iterate through all keys and values of an object. * * Adding keys to the object while iterating is NOT allowed. * @@ -577,14 +637,14 @@ void json_object_object_del_len(struct json_object *jso, const char *key, const (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) #define json_object_object_foreach(obj, key, val) \ - struct lh_string *key = NULL; \ + struct json_key *key = NULL; \ struct json_object *val __attribute__((__unused__)) = NULL; \ for (struct lh_entry *entry##key = json_object_get_object(obj)->head, \ *entry_next##key = NULL; \ ({ \ if (entry##key) \ { \ - key = (struct lh_string *)lh_entry_k(entry##key); \ + key = (struct json_key *)lh_entry_k(entry##key); \ val = (struct json_object *)lh_entry_v(entry##key); \ entry_next##key = entry##key->next; \ }; \ @@ -608,13 +668,14 @@ void json_object_object_del_len(struct json_object *jso, const char *key, const #endif /* defined(__GNUC__) && !defined(__STRICT_ANSI__) && (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) */ -/** Iterate through all keys and values of an object (ANSI C Safe) +/** + * @brief Iterate through all keys and values of an object (ANSI C Safe) * @param obj the json_object instance * @param iter the object iterator, use type json_object_iter */ #define json_object_object_foreachC(obj, iter) \ for (iter.entry = json_object_get_object(obj)->head; \ - (iter.entry ? (iter.key = (struct lh_string *)lh_entry_k(iter.entry), \ + (iter.entry ? (iter.key = (struct json_key *)lh_entry_k(iter.entry), \ iter.val = (struct json_object *)lh_entry_v(iter.entry), iter.entry) \ : 0); \ iter.entry = iter.entry->next) @@ -1129,7 +1190,7 @@ JSON_EXPORT int json_object_equal(struct json_object *obj1, struct json_object * * @return On success 1 or 2, -1 on errors */ typedef int(json_c_shallow_copy_fn)(json_object *src, json_object *parent, - const struct lh_string *key, size_t index, json_object **dst); + const struct json_key *key, size_t index, json_object **dst); /** * The default shallow copy implementation for use with json_object_deep_copy(). @@ -1166,6 +1227,53 @@ JSON_EXPORT json_c_shallow_copy_fn json_c_shallow_copy_default; JSON_EXPORT int json_object_deep_copy(struct json_object *src, struct json_object **dst, json_c_shallow_copy_fn *shallow_copy); + +/* Json Object Keys */ + +/** + * @brief Get the length of the data stored in a `struct json_key *` + * + * @param str value to retrieve the length of + */ +JSON_EXPORT size_t json_key_size(const struct json_key *str); + +/** + * @brief Get the data from a `struct json_key *` + * + * @param str value to retrieve the data from + */ +JSON_EXPORT const char *json_key_data(const struct json_key *str); + +/** + * @brief Creates a new `struct json_key` that uses the `pdata` field. + * + * This avoids unneeded copying, for cases wher ethe key is in an area of + * memory that will not be modified or freed until after this object is freed. + * + * @param length The length of the data located at @p data + * @param data The data to include + * @return On success, a pointer to the new key is returned. + * On error, a null pointer is returned. + */ +JSON_EXPORT struct json_key *json_key_new_ptr(const size_t length, const char *data); + +/** + * @brief Creates a new `struct json_key` that uses the `idata` field. + * + * @param length The length of the data to copy into the returned value + * @param data The data to include and copy into the returned value + * @return On success, a pointer to the new key is returned. + * On error, a null pointer is returned. + */ +JSON_EXPORT struct json_key *json_key_new_imm(const size_t length, const char *data); + +/** + * @brief Frees the memory associated with a `struct json_key` + * + * @param key The data to free + */ +JSON_EXPORT void json_key_del(const struct json_key *key); + #ifdef __cplusplus } #endif diff --git a/json_object_iterator.c b/json_object_iterator.c index 0f065a4..754192a 100644 --- a/json_object_iterator.c +++ b/json_object_iterator.c @@ -104,12 +104,12 @@ void json_object_iter_next(struct json_object_iterator *iter) /** * **************************************************************************** */ -const struct lh_string *json_object_iter_peek_name(const struct json_object_iterator *iter) +const struct json_key *json_object_iter_peek_name(const struct json_object_iterator *iter) { JASSERT(NULL != iter); JASSERT(kObjectEndIterValue != iter->opaque_); - return (const struct lh_string *)(((const struct lh_entry *)iter->opaque_)->k); + return (const struct json_key *)(((const struct lh_entry *)iter->opaque_)->k); } /** diff --git a/json_object_iterator.h b/json_object_iterator.h index 8b56479..2e1f969 100644 --- a/json_object_iterator.h +++ b/json_object_iterator.h @@ -168,7 +168,7 @@ JSON_EXPORT void json_object_iter_next(struct json_object_iterator *iter); * deleted or modified, and MUST NOT be modified or * freed by the user. */ -JSON_EXPORT const struct lh_string * +JSON_EXPORT const struct json_key * json_object_iter_peek_name(const struct json_object_iterator *iter); /** Returns a pointer to the json-c instance representing the diff --git a/json_object_private.h b/json_object_private.h index 5b08dd1..6ad1044 100644 --- a/json_object_private.h +++ b/json_object_private.h @@ -98,27 +98,53 @@ struct json_object_string void _json_c_set_last_err(const char *err_fmt, ...); -/** - * @brief Add an object field to a json_object of type json_type_object - * - * The semantics are identical to json_object_object_add_ex, except that @p key - * contains both the data and the length. - * - * @param obj the json_object instance - * @param key the object field name (a private copy will be duplicated) - * @param val a json_object or NULL member to associate with the given field - * @param opts process-modifying options. To specify multiple options, use - * (OPT1|OPT2) - * @return On success, @c 0 is returned. - * On error, a negative value is returned. - */ -int json_object_object_add_internal(struct json_object *obj, const struct lh_string *key, - struct json_object *const val, const unsigned opts); /** * The characters that can make up hexadecimal numbers */ extern const char *json_hex_chars; +/** + * @brief A buffer of characters that may contain null charaters in the middle + * + * A buffer of data that can hold a normal null-terminated string + * (in which case `length` should just be equal to `strlen`) + * or a string with embedded null characters (in which case `length` reflects + * all the characters that make up the "string"). + * Either way, this struct can be treated as if it contains null characters, + * since the `length` member should always be equal to the proper size of the + * buffer and the terminating null character wouldn't be included + * (it wouldn't be counted by strlen). + */ +struct json_key +{ + /** + * @brief Stores the length of the buffer + * + * If the length is positive, then `pdata` should be used. + * Otherwise, idata should be used. + */ + ssize_t length; + union + { + /** + * @brief A pointer to data that is stored elsewhere + * + * If the data stored there will not change for the lifetime of + * the key, use `pdata` rather than `idata`. + */ + const char *pdata; + /** + * @brief Data stored inline + * + * If the data stored may be overwritten, such as if it is + * copied from the stack, this struct should be allocated with + * enough space to store the whole string (of length `len`) + * and one additional null character. + */ + const char idata[0]; + } str; +}; + #ifdef __cplusplus } #endif diff --git a/json_tokener.c b/json_tokener.c index 8fea883..185d06c 100644 --- a/json_tokener.c +++ b/json_tokener.c @@ -1106,8 +1106,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * printbuf_memappend_fast(tok->pb, case_start, str - case_start); obj_field_name = - lh_string_new_imm(tok->pb->bpos, tok->pb->buf); - // lh_string_print(obj_field_name, stdout); + json_key_new_imm(tok->pb->bpos, tok->pb->buf); saved_state = json_tokener_state_object_field_end; state = json_tokener_state_eatws; break; @@ -1155,7 +1154,7 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char * goto redo_char; case json_tokener_state_object_value_add: - json_object_object_add_internal(current, obj_field_name, obj, 0); + json_object_object_add_key(current, obj_field_name, obj, 0); free(obj_field_name); obj_field_name = NULL; saved_state = json_tokener_state_object_sep; diff --git a/json_tokener.h b/json_tokener.h index 9a0d411..984016a 100644 --- a/json_tokener.h +++ b/json_tokener.h @@ -17,7 +17,6 @@ #define _json_tokener_h_ #include "json_object.h" -#include "linkhash.h" #include #ifdef __cplusplus @@ -86,7 +85,7 @@ struct json_tokener_srec enum json_tokener_state state, saved_state; struct json_object *obj; struct json_object *current; - struct lh_string *obj_field_name; + struct json_key *obj_field_name; }; #define JSON_TOKENER_DEFAULT_DEPTH 32 diff --git a/json_types.h b/json_types.h index 2892ef3..a7366f1 100644 --- a/json_types.h +++ b/json_types.h @@ -33,7 +33,7 @@ struct printbuf; */ struct json_object_iter { - struct lh_string *key; + struct json_key *key; struct json_object *val; struct lh_entry *entry; }; @@ -71,6 +71,8 @@ typedef enum json_type json_type_string } json_type; +typedef struct json_key json_key; + #ifdef __cplusplus } #endif diff --git a/json_visit.c b/json_visit.c index 43f2ec0..63dc797 100644 --- a/json_visit.c +++ b/json_visit.c @@ -13,7 +13,7 @@ #include "json_visit.h" #include "linkhash.h" -static int _json_c_visit(json_object *jso, json_object *parent_jso, const struct lh_string *jso_key, +static int _json_c_visit(json_object *jso, json_object *parent_jso, const struct json_key *jso_key, size_t *jso_index, json_c_visit_userfunc *userfunc, void *userarg); int json_c_visit(json_object *jso, int future_flags, json_c_visit_userfunc *userfunc, void *userarg) @@ -28,7 +28,7 @@ int json_c_visit(json_object *jso, int future_flags, json_c_visit_userfunc *user default: return JSON_C_VISIT_RETURN_ERROR; } } -static int _json_c_visit(json_object *jso, json_object *parent_jso, const struct lh_string *jso_key, +static int _json_c_visit(json_object *jso, json_object *parent_jso, const struct json_key *jso_key, size_t *jso_index, json_c_visit_userfunc *userfunc, void *userarg) { int userret = userfunc(jso, 0, parent_jso, jso_key, jso_index, userarg); diff --git a/json_visit.h b/json_visit.h index 21f0013..5edc7a9 100644 --- a/json_visit.h +++ b/json_visit.h @@ -13,7 +13,7 @@ extern "C" { #endif typedef int(json_c_visit_userfunc)(json_object *jso, int flags, json_object *parent_jso, - const struct lh_string *jso_key, size_t *jso_index, + const struct json_key *jso_key, size_t *jso_index, void *userarg); /** diff --git a/linkhash.c b/linkhash.c index 4bb9844..da5d641 100644 --- a/linkhash.c +++ b/linkhash.c @@ -486,62 +486,14 @@ static unsigned long lh_char_hash(const void *k) random_seed = seed; /* potentially racy */ #endif } - return hashlittle(lh_string_data((const struct lh_string *)k), - lh_string_size((const struct lh_string *)k), random_seed); + return hashlittle(json_key_data((const struct json_key *)k), + json_key_size((const struct json_key *)k), random_seed); } int lh_char_equal(const void *k1, const void *k2) { - return lh_string_size(k1) == lh_string_size(k2) && - memcmp(lh_string_data(k1), lh_string_data(k2), lh_string_size(k1)) == 0; -} - -const char *lh_string_data(const struct lh_string *str) -{ - return (str->length > 0) ? str->str.pdata : str->str.idata; -} - -size_t lh_string_size(const struct lh_string *str) -{ - return (str->length > 0) ? (size_t)str->length : (size_t)(-(str->length)); -} - -size_t lh_string_print(const struct lh_string *key, FILE *stream) -{ - return fwrite(lh_string_data(key), lh_string_size(key), 1, stream); -} - -struct lh_string *lh_string_new_ptr(const size_t length, const char *data) -{ - struct lh_string *result = malloc(sizeof(struct lh_string)); - if (result == NULL) - { - return NULL; - } - result->length = length; - result->str.pdata = data; - return result; -} - -struct lh_string *lh_string_new_imm(const size_t length, const char *data) -{ - struct lh_string *result; - if (length > - SSIZE_T_MAX - (sizeof(struct lh_string) - sizeof(((struct lh_string *)NULL)->str)) - 1) - { - return NULL; - } - result = - malloc(sizeof(struct lh_string) - sizeof(((struct lh_string *)NULL)->str) + length + 1); - if (result == NULL) - { - return NULL; - } - result->length = -length; - char *unconst = _LH_UNCONST(result->str.idata); - memcpy(unconst, data, length); - unconst[length] = '\0'; - return result; + return json_key_size(k1) == json_key_size(k2) && + memcmp(json_key_data(k1), json_key_data(k2), json_key_size(k1)) == 0; } struct lh_table *lh_table_new(int size, lh_entry_free_fn *free_fn, lh_hash_fn *hash_fn, diff --git a/linkhash.h b/linkhash.h index a8b82f2..25c980e 100644 --- a/linkhash.h +++ b/linkhash.h @@ -20,7 +20,6 @@ #define _json_c_linkhash_h_ #include "json_object.h" -#include #ifdef __cplusplus extern "C" { @@ -80,34 +79,6 @@ typedef unsigned long(lh_hash_fn)(const void *k); */ typedef int(lh_equal_fn)(const void *k1, const void *k2); -/** - * @brief A buffer of characters that may contain null charaters in the middle - * - * A buffer of data that can hold a normal null-terminated string - * (in which case `length` should just be equal to `strlen`) - * or a string with embedded null characters (in which case `length` reflects - * all the characters that make up the "string"). - * Either way, this struct can be treated as if it contains null characters, - * since the `length` member should always be equal to the proper size of the - * buffer and the terminating null character wouldn't be included - * (it wouldn't be counted by strlen). - */ -struct lh_string -{ - /** - * @brief Stores the length of the buffer - * - * If the length is positive, then `pdata` should be used. - * Otherwise, idata should be used. - */ - ssize_t length; - union - { - const char *pdata; - const char idata[0]; - } str; -}; - /** * An entry in the hash table */ @@ -193,49 +164,6 @@ typedef struct lh_table lh_table; #define lh_foreach_safe(table, entry, tmp) \ for (entry = table->head; entry && ((tmp = entry->next) || 1); entry = tmp) -/** - * @brief Get the data from a `struct lh_string *` - * - * @param str value to retrieve the data from - */ -extern const char *lh_string_data(const struct lh_string *str); - -/** - * @brief Get the length of the data stored in a `struct lh_string *` - * - * @param str value to retrieve the length of - */ -extern size_t lh_string_size(const struct lh_string *str); - -/** - * @brief Print a `struct lh_string` to a given stream - * - * @param str value to print - * @param stream Stream to write data to - */ -extern size_t lh_string_print(const struct lh_string *str, FILE *stream); - -/** - * @brief Creates a new `struct lh_string` using the `pdata` field. - * - * This avoids unneeded copying, for cases wher ethe key is in an area of - * memory that will not be modified or freed until after this object is freed. - * - * @param length The length of the data located at @p data - * @param data The data to include - * @return `NULL` on error - */ -extern struct lh_string *lh_string_new_ptr(const size_t length, const char *data); - -/** - * @brief Creates a new `struct lh_string` using the `idata` field. - * - * @param length The length of the data to copy into the returned value - * @param data The data to include and copy into the returned value - * @return `NULL` on error - */ -extern struct lh_string *lh_string_new_imm(const size_t length, const char *data); - /** * Create a new linkhash table. * diff --git a/tests/test1.c b/tests/test1.c index aa156f3..f061961 100644 --- a/tests/test1.c +++ b/tests/test1.c @@ -307,7 +307,7 @@ int main(int argc, char **argv) json_object_object_foreach(my_object, key, val) { putchar('\t'); - lh_string_print(key, stdout); + fwrite(json_key_data(key), json_key_size(key), 1, stdout); printf(": %s\n", json_object_to_json_string(val)); } printf("my_object.to_string()=%s\n", json_object_to_json_string(my_object)); diff --git a/tests/testReplaceExisting.c b/tests/testReplaceExisting.c index 5d99212..70369d3 100644 --- a/tests/testReplaceExisting.c +++ b/tests/testReplaceExisting.c @@ -24,11 +24,11 @@ int main(int argc, char **argv) int orig_count = 0; json_object_object_foreach(my_object, key0, val0) { - printf("Key at index %d is [%s] %d", orig_count, lh_string_data(key0), + printf("Key at index %d is [%s] %d", orig_count, json_key_data(key0), (val0 == NULL)); - if (strcmp(lh_string_data(key0), "deleteme") == 0) + if (strcmp(json_key_data(key0), "deleteme") == 0) { - json_object_object_del(my_object, lh_string_data(key0)); + json_object_object_del(my_object, json_key_data(key0)); printf(" (deleted)\n"); } else @@ -38,18 +38,18 @@ int main(int argc, char **argv) printf("==== replace-value first loop starting ====\n"); - const struct lh_string *original_key = NULL; + const struct json_key *original_key = NULL; orig_count = 0; json_object_object_foreach(my_object, key, val) { - printf("Key at index %d is [%s] %d\n", orig_count, lh_string_data(key), + printf("Key at index %d is [%s] %d\n", orig_count, json_key_data(key), (val == NULL)); orig_count++; - if (strcmp(lh_string_data(key), "foo2") != 0) + if (strcmp(json_key_data(key), "foo2") != 0) continue; - printf("replacing value for key [%s]\n", lh_string_data(key)); + printf("replacing value for key [%s]\n", json_key_data(key)); original_key = key; - json_object_object_add(my_object, lh_string_data(key0), + json_object_object_add(my_object, json_key_data(key0), json_object_new_string("zzz")); } @@ -59,12 +59,12 @@ int main(int argc, char **argv) int retval = 0; json_object_object_foreach(my_object, key2, val2) { - printf("Key at index %d is [%s] %d\n", new_count, lh_string_data(key2), + printf("Key at index %d is [%s] %d\n", new_count, json_key_data(key2), (val2 == NULL)); new_count++; - if (strcmp(lh_string_data(key2), "foo2") != 0) + if (strcmp(json_key_data(key2), "foo2") != 0) continue; - printf("pointer for key [%s] does %smatch\n", lh_string_data(key2), + printf("pointer for key [%s] does %smatch\n", json_key_data(key2), (key2 == original_key) ? "" : "NOT "); if (key2 != original_key) retval = 1; diff --git a/tests/test_deep_copy.c b/tests/test_deep_copy.c index b1c685e..421cb4f 100644 --- a/tests/test_deep_copy.c +++ b/tests/test_deep_copy.c @@ -93,14 +93,14 @@ int my_custom_serializer(struct json_object *jso, struct printbuf *pb, int level } json_c_shallow_copy_fn my_shallow_copy; -int my_shallow_copy(json_object *src, json_object *parent, const struct lh_string *key, - size_t index, json_object **dst) +int my_shallow_copy(json_object *src, json_object *parent, const struct json_key *key, size_t index, + json_object **dst) { int rc; rc = json_c_shallow_copy_default(src, parent, key, index, dst); if (rc < 0) return rc; - if (key != NULL && strcmp(lh_string_data(key), "with_serializer") == 0) + if (key != NULL && strcmp(json_key_data(key), "with_serializer") == 0) { printf("CALLED: my_shallow_copy on with_serializer object\n"); void *userdata = json_object_get_userdata(src); diff --git a/tests/test_null_keys_add.c b/tests/test_null_keys_add.c index a2965cd..51a21d8 100644 --- a/tests/test_null_keys_add.c +++ b/tests/test_null_keys_add.c @@ -114,8 +114,8 @@ int main(void) json_object_object_foreach(parsed, key, val) { putchar('\"'); - fwrite(lh_string_data(key), lh_string_size(key), 1, stdout); - printf("\" (%zd)\n", lh_string_size(key)); + fwrite(json_key_data(key), json_key_size(key), 1, stdout); + printf("\" (%zd)\n", json_key_size(key)); } return 1; } diff --git a/tests/test_null_keys_del.expected b/tests/test_null_keys_del.expected index 44f1682..043cd0c 100644 --- a/tests/test_null_keys_del.expected +++ b/tests/test_null_keys_del.expected @@ -1,5 +1,5 @@ Parsed input: { "biff\u0000": null, "\u0000zxcvbnm": 178 } Result is `json_type_object` -Expected keys ("biff\0" and "\0zxcvbnm") present with expected values +Expected keys ("biff\u0000" and "\u0000zxcvbnm") present with expected values Key deleted properly PASS diff --git a/tests/test_object_iterator.c b/tests/test_object_iterator.c index 9e0a178..755dd5d 100644 --- a/tests/test_object_iterator.c +++ b/tests/test_object_iterator.c @@ -31,7 +31,7 @@ int main(int atgc, char **argv) while (!json_object_iter_equal(&it, &itEnd)) { - printf("%s\n", lh_string_data(json_object_iter_peek_name(&it))); + printf("%s\n", json_key_data(json_object_iter_peek_name(&it))); printf("%s\n", json_object_to_json_string(json_object_iter_peek_value(&it))); json_object_iter_next(&it); } diff --git a/tests/test_parse.c b/tests/test_parse.c index 19238c4..df33d38 100644 --- a/tests/test_parse.c +++ b/tests/test_parse.c @@ -215,7 +215,7 @@ static void do_clear_serializer(json_object *jso) } static int clear_serializer(json_object *jso, int flags, json_object *parent_jso, - const struct lh_string *jso_key, size_t *jso_index, void *userarg) + const struct json_key *jso_key, size_t *jso_index, void *userarg) { if (jso) json_object_set_serializer(jso, NULL, NULL, NULL); diff --git a/tests/test_visit.c b/tests/test_visit.c index b226d87..fd4aba5 100644 --- a/tests/test_visit.c +++ b/tests/test_visit.c @@ -69,16 +69,16 @@ int main(void) } static int emit_object(json_object *jso, int flags, json_object *parent_jso, - const struct lh_string *jso_key, size_t *jso_index, void *userarg) + const struct json_key *jso_key, size_t *jso_index, void *userarg) { printf("flags: 0x%x, key: %s, index: %ld, value: %s\n", flags, - (jso_key ? lh_string_data(jso_key) : "(null)"), (jso_index ? (long)*jso_index : -1L), + (jso_key ? json_key_data(jso_key) : "(null)"), (jso_index ? (long)*jso_index : -1L), json_object_to_json_string(jso)); return JSON_C_VISIT_RETURN_CONTINUE; } static int skip_arrays(json_object *jso, int flags, json_object *parent_jso, - const struct lh_string *jso_key, size_t *jso_index, void *userarg) + const struct json_key *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); if (json_object_get_type(jso) == json_type_array) @@ -87,15 +87,15 @@ static int skip_arrays(json_object *jso, int flags, json_object *parent_jso, } static int pop_and_stop(json_object *jso, int flags, json_object *parent_jso, - const struct lh_string *jso_key, size_t *jso_index, void *userarg) + const struct json_key *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); - if (jso_key != NULL && strcmp(lh_string_data(jso_key), "subobj1") == 0) + if (jso_key != NULL && strcmp(json_key_data(jso_key), "subobj1") == 0) { printf("POP after handling subobj1\n"); return JSON_C_VISIT_RETURN_POP; } - if (jso_key != NULL && strcmp(lh_string_data(jso_key), "obj3") == 0) + if (jso_key != NULL && strcmp(json_key_data(jso_key), "obj3") == 0) { printf("STOP after handling obj3\n"); return JSON_C_VISIT_RETURN_STOP; @@ -104,10 +104,10 @@ static int pop_and_stop(json_object *jso, int flags, json_object *parent_jso, } static int err_on_subobj2(json_object *jso, int flags, json_object *parent_jso, - const struct lh_string *jso_key, size_t *jso_index, void *userarg) + const struct json_key *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); - if (jso_key != NULL && strcmp(lh_string_data(jso_key), "subobj2") == 0) + if (jso_key != NULL && strcmp(json_key_data(jso_key), "subobj2") == 0) { printf("ERROR after handling subobj1\n"); return JSON_C_VISIT_RETURN_ERROR; @@ -116,7 +116,7 @@ static int err_on_subobj2(json_object *jso, int flags, json_object *parent_jso, } static int pop_array(json_object *jso, int flags, json_object *parent_jso, - const struct lh_string *jso_key, size_t *jso_index, void *userarg) + const struct json_key *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); if (jso_index != NULL && (*jso_index == 0)) @@ -128,7 +128,7 @@ static int pop_array(json_object *jso, int flags, json_object *parent_jso, } static int stop_array(json_object *jso, int flags, json_object *parent_jso, - const struct lh_string *jso_key, size_t *jso_index, void *userarg) + const struct json_key *jso_key, size_t *jso_index, void *userarg) { (void)emit_object(jso, flags, parent_jso, jso_key, jso_index, userarg); if (jso_index != NULL && (*jso_index == 0)) @@ -140,10 +140,10 @@ static int stop_array(json_object *jso, int flags, json_object *parent_jso, } static int err_return(json_object *jso, int flags, json_object *parent_jso, - const struct lh_string *jso_key, size_t *jso_index, void *userarg) + const struct json_key *jso_key, size_t *jso_index, void *userarg) { printf("flags: 0x%x, key: %s, index: %ld, value: %s\n", flags, - (jso_key ? lh_string_data(jso_key) : "(null)"), (jso_index ? (long)*jso_index : -1L), + (jso_key ? json_key_data(jso_key) : "(null)"), (jso_index ? (long)*jso_index : -1L), json_object_to_json_string(jso)); return 100; } From 65e0969db01e4e6c362a36147d09311a9bee80ae Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Sun, 11 Jul 2021 13:53:26 -0800 Subject: [PATCH 08/11] Modifications so it compiles on Windows correctly --- json_object.c | 25 ++++++++++++------------- json_object.h | 31 +++++++++++++++++-------------- json_object_private.h | 2 +- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/json_object.c b/json_object.c index b48b889..3a5d860 100644 --- a/json_object.c +++ b/json_object.c @@ -574,13 +574,13 @@ int json_object_object_add_ex(struct json_object *jso, const char *const key, return json_object_object_add_ex_len(jso, key, strlen(key), val, opts); } -int json_object_object_add_ex_len(struct json_object *jso, const char *const key, const int len, +int json_object_object_add_ex_len(struct json_object *jso, const char *const key, const size_t len, struct json_object *const val, const unsigned opts) { // Created on the stack rather than calling `json_key_new_ptr` // or `json_key_new_imm` since this saves copying `key` if it turns // out the value already exists in the hash table - const struct json_key hashable = {.length = len, .str = {.pdata = key}}; + struct json_key hashable = {len, {key}}; return json_object_object_add_key(jso, &hashable, val, opts); } @@ -646,7 +646,7 @@ int json_object_object_add(struct json_object *jso, const char *key, struct json return json_object_object_add_ex_len(jso, key, strlen(key), val, 0); } -int json_object_object_add_len(struct json_object *jso, const char *key, const int len, +int json_object_object_add_len(struct json_object *jso, const char *key, const size_t len, struct json_object *val) { return json_object_object_add_ex_len(jso, key, len, val, 0); @@ -671,7 +671,7 @@ struct json_object *json_object_object_get(const struct json_object *jso, const } struct json_object *json_object_object_get_len(const struct json_object *jso, const char *key, - const int len) + const size_t len) { struct json_object *result = NULL; json_object_object_get_ex_len(jso, key, len, &result); @@ -685,7 +685,7 @@ json_bool json_object_object_get_ex(const struct json_object *jso, const char *k } json_bool json_object_object_get_ex_len(const struct json_object *jso, const char *key, - const int len, struct json_object **value) + const size_t len, struct json_object **value) { if (value != NULL) *value = NULL; @@ -697,7 +697,7 @@ json_bool json_object_object_get_ex_len(const struct json_object *jso, const cha { case json_type_object: { - const struct json_key hashable = {.length = len, .str = {.pdata = key}}; + const struct json_key hashable = {len, {key}}; return json_object_object_get_key(jso, &hashable, value); } default: @@ -719,9 +719,9 @@ void json_object_object_del(struct json_object *jso, const char *key) json_object_object_del_len(jso, key, strlen(key)); } -void json_object_object_del_len(struct json_object *jso, const char *key, const int len) +void json_object_object_del_len(struct json_object *jso, const char *key, const size_t len) { - const struct json_key hashable = {.length = len, .str = {.pdata = key}}; + const struct json_key hashable = {len, {key}}; json_object_object_del_key(jso, &hashable); } @@ -1888,7 +1888,7 @@ struct json_key *json_key_new_ptr(const size_t length, const char *data) { return NULL; } - result->length = length; + result->length = (ssize_t)length; result->str.pdata = data; return result; } @@ -1907,9 +1907,8 @@ struct json_key *json_key_new_imm(const size_t length, const char *data) { return NULL; } - result->length = -length; - char *unconst = _LH_UNCONST(result->str.idata); - memcpy(unconst, data, length); - unconst[length] = '\0'; + result->length = -((ssize_t)length); + memcpy(result->str.idata, data, length); + result->str.idata[length] = '\0'; return result; } diff --git a/json_object.h b/json_object.h index 771ea73..70ebcef 100644 --- a/json_object.h +++ b/json_object.h @@ -357,7 +357,8 @@ JSON_EXPORT int json_object_object_length(const struct json_object *obj); */ JSON_C_CONST_FUNCTION(JSON_EXPORT size_t json_c_object_sizeof(void)); -/** Add an object field to a json_object of type json_type_object +/** + * @brief Add an object field to a json_object of type json_type_object * * The reference count of `val` will *not* be incremented, in effect * transferring ownership that object to `obj`, and thus `val` will be @@ -385,7 +386,8 @@ JSON_C_CONST_FUNCTION(JSON_EXPORT size_t json_c_object_sizeof(void)); JSON_EXPORT int json_object_object_add(struct json_object *obj, const char *key, struct json_object *val); -/** Add an object field to a json_object of type json_type_object +/** + * @brief Add an object field to a json_object of type json_type_object * * The reference count of `val` will *not* be incremented, in effect * transferring ownership that object to `obj`, and thus `val` will be @@ -412,10 +414,11 @@ JSON_EXPORT int json_object_object_add(struct json_object *obj, const char *key, * @return On success, @c 0 is returned. * On error, a negative value is returned. */ -JSON_EXPORT int json_object_object_add_len(struct json_object *obj, const char *key, const int len, - struct json_object *val); +JSON_EXPORT int json_object_object_add_len(struct json_object *obj, const char *key, + const size_t len, struct json_object *val); -/** Add an object field to a json_object of type json_type_object +/** + * @brief Add an object field to a json_object of type json_type_object * * The semantics are identical to json_object_object_add, except that an * additional flag fields gives you more control over some detail aspects @@ -434,7 +437,8 @@ JSON_EXPORT int json_object_object_add_len(struct json_object *obj, const char * JSON_EXPORT int json_object_object_add_ex(struct json_object *obj, const char *const key, struct json_object *const val, const unsigned opts); -/** Add an object field to a json_object of type json_type_object +/** + * @brief Add an object field to a json_object of type json_type_object * * The semantics are identical to json_object_object_add, except that an * additional flag fields gives you more control over some detail aspects @@ -453,7 +457,7 @@ JSON_EXPORT int json_object_object_add_ex(struct json_object *obj, const char *c * On error, a negative value is returned. */ JSON_EXPORT int json_object_object_add_ex_len(struct json_object *obj, const char *const key, - const int len, struct json_object *const val, + const size_t len, struct json_object *const val, const unsigned opts); /** @@ -515,7 +519,7 @@ JSON_EXPORT struct json_object *json_object_object_get(const struct json_object */ JSON_EXPORT struct json_object *json_object_object_get_len(const struct json_object *obj, - const char *key, const int len); + const char *key, const size_t len); /** * @brief Get the json_object associated with a given object field. @@ -557,7 +561,7 @@ JSON_EXPORT json_bool json_object_object_get_ex(const struct json_object *obj, c * @returns whether or not the key exists */ JSON_EXPORT json_bool json_object_object_get_ex_len(const struct json_object *obj, const char *key, - const int len, struct json_object **value); + const size_t len, struct json_object **value); /** * @brief Get the json_object associated with a given object field. @@ -605,7 +609,7 @@ JSON_EXPORT void json_object_object_del(struct json_object *obj, const char *key * @param len the length of @p key */ JSON_EXPORT void json_object_object_del_len(struct json_object *jso, const char *key, - const int len); + const size_t len); /** * @brief Delete the given json_object field @@ -617,8 +621,7 @@ JSON_EXPORT void json_object_object_del_len(struct json_object *jso, const char * @param obj the json_object instance * @param key the object field name */ -JSON_EXPORT int json_object_object_del_key(struct json_object *jso_base, - const struct json_key *key); +JSON_EXPORT int json_object_object_del_key(struct json_object *obj, const struct json_key *key); /** * @brief Iterate through all keys and values of an object. @@ -655,12 +658,12 @@ JSON_EXPORT int json_object_object_del_key(struct json_object *jso_base, #else /* ANSI C or MSC */ #define json_object_object_foreach(obj, key, val) \ - char *key = NULL; \ + struct json_key *key = NULL; \ struct json_object *val = NULL; \ struct lh_entry *entry##key; \ struct lh_entry *entry_next##key = NULL; \ for (entry##key = json_object_get_object(obj)->head; \ - (entry##key ? (key = (char *)lh_entry_k(entry##key), \ + (entry##key ? (key = (struct json_key *)lh_entry_k(entry##key), \ val = (struct json_object *)lh_entry_v(entry##key), \ entry_next##key = entry##key->next, entry##key) \ : 0); \ diff --git a/json_object_private.h b/json_object_private.h index 6ad1044..cae49f7 100644 --- a/json_object_private.h +++ b/json_object_private.h @@ -141,7 +141,7 @@ struct json_key * enough space to store the whole string (of length `len`) * and one additional null character. */ - const char idata[0]; + char idata[0]; } str; }; From 95225e6a325f3bc4aab33ce5f90fe4958435e4d8 Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Sun, 18 Jul 2021 13:05:23 -0800 Subject: [PATCH 09/11] Move a header back that shouldn't have been moved I moved this before when I was using ssize_t in linkhash.c, but I moved `struct lh_string` to `struct json_key` in a different file, so this isn't needed anymore --- json_object.c | 15 ++++++++++++++- math_compat.h | 13 ------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/json_object.c b/json_object.c index 3a5d860..b56c14b 100644 --- a/json_object.c +++ b/json_object.c @@ -39,6 +39,19 @@ #error "The long long type isn't 64-bits" #endif +#include +#ifndef SSIZE_T_MAX +#if SIZEOF_SSIZE_T == SIZEOF_INT +#define SSIZE_T_MAX INT_MAX +#elif SIZEOF_SSIZE_T == SIZEOF_LONG +#define SSIZE_T_MAX LONG_MAX +#elif SIZEOF_SSIZE_T == SIZEOF_LONG_LONG +#define SSIZE_T_MAX LLONG_MAX +#else +#error Unable to determine size of ssize_t +#endif +#endif + // Don't define this. It's not thread-safe. /* #define REFCOUNT_DEBUG 1 */ @@ -1215,7 +1228,7 @@ static int _json_object_userdata_to_json_string(struct json_object *jso, struct int json_object_userdata_to_json_string(struct json_object *jso, struct printbuf *pb, int level, int flags) { - int userdata_len = strlen((const char *)jso->_userdata); + size_t userdata_len = strlen((const char *)jso->_userdata); printbuf_memappend(pb, (const char *)jso->_userdata, userdata_len); return userdata_len; } diff --git a/math_compat.h b/math_compat.h index 9894776..2382fe1 100644 --- a/math_compat.h +++ b/math_compat.h @@ -40,17 +40,4 @@ #define HAVE_DECL_NAN #endif -#include -#ifndef SSIZE_T_MAX -#if SIZEOF_SSIZE_T == SIZEOF_INT -#define SSIZE_T_MAX INT_MAX -#elif SIZEOF_SSIZE_T == SIZEOF_LONG -#define SSIZE_T_MAX LONG_MAX -#elif SIZEOF_SSIZE_T == SIZEOF_LONG_LONG -#define SSIZE_T_MAX LLONG_MAX -#else -#error Unable to determine size of ssize_t -#endif -#endif - #endif From 85ca6f83930d539cbf98d7ca8f54a5d8f02a9fd1 Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Sun, 18 Jul 2021 13:08:07 -0800 Subject: [PATCH 10/11] Keep `json_object_object_foreach` the same --- json_object.h | 55 ++++++++++++++++++++++++++++++++++--- json_visit.c | 2 +- tests/test1.c | 2 +- tests/testReplaceExisting.c | 13 ++++----- tests/test_null_keys_add.c | 2 +- 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/json_object.h b/json_object.h index 70ebcef..bc08554 100644 --- a/json_object.h +++ b/json_object.h @@ -632,14 +632,61 @@ JSON_EXPORT int json_object_object_del_key(struct json_object *obj, const struct * new value IS allowed. * * @param obj the json_object instance - * @param key the local name for the char* key variable defined in the body - * @param val the local name for the json_object* object variable defined in + * @param key the local name for the `char *` key variable defined in the body + * @param val the local name for the `json_object *` object variable defined in * the body */ #if defined(__GNUC__) && !defined(__STRICT_ANSI__) && \ (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) -#define json_object_object_foreach(obj, key, val) \ +#define json_object_object_foreach(obj, key, val) \ + const char *key = NULL; \ + struct json_object *val __attribute__((__unused__)) = NULL; \ + for (struct lh_entry *entry##key = json_object_get_object(obj)->head, \ + *entry_next##key = NULL; \ + ({ \ + if (entry##key) \ + { \ + key = json_key_data((struct json_key *)lh_entry_k(entry##key)); \ + val = (struct json_object *)lh_entry_v(entry##key); \ + entry_next##key = entry##key->next; \ + }; \ + entry##key; \ + }); \ + entry##key = entry_next##key) + +#else /* ANSI C or MSC */ + +#define json_object_object_foreach(obj, key, val) \ + const char *key = NULL; \ + struct json_object *val = NULL; \ + struct lh_entry *entry##key; \ + struct lh_entry *entry_next##key = NULL; \ + for (entry##key = json_object_get_object(obj)->head; \ + (entry##key ? (key = json_key_data((struct json_key *)lh_entry_k(entry##key)), \ + val = (struct json_object *)lh_entry_v(entry##key), \ + entry_next##key = entry##key->next, entry##key) \ + : 0); \ + entry##key = entry_next##key) + +#endif /* defined(__GNUC__) && !defined(__STRICT_ANSI__) && (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) */ +/** + * @brief Iterate through all keys and values of an object. + * + * Adding keys to the object while iterating is NOT allowed. + * + * Deleting an existing key, or replacing an existing key with a + * new value IS allowed. + * + * @param obj the json_object instance + * @param key the local name for the `struct json_key *` key variable defined in the body + * @param val the local name for the `json_object *` object variable defined in + * the body + */ +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) && \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) + +#define json_object_object_foreach_len(obj, key, val) \ struct json_key *key = NULL; \ struct json_object *val __attribute__((__unused__)) = NULL; \ for (struct lh_entry *entry##key = json_object_get_object(obj)->head, \ @@ -657,7 +704,7 @@ JSON_EXPORT int json_object_object_del_key(struct json_object *obj, const struct #else /* ANSI C or MSC */ -#define json_object_object_foreach(obj, key, val) \ +#define json_object_object_foreach_len(obj, key, val) \ struct json_key *key = NULL; \ struct json_object *val = NULL; \ struct lh_entry *entry##key; \ diff --git a/json_visit.c b/json_visit.c index 63dc797..41d0a47 100644 --- a/json_visit.c +++ b/json_visit.c @@ -57,7 +57,7 @@ static int _json_c_visit(json_object *jso, json_object *parent_jso, const struct case json_type_object: { - json_object_object_foreach(jso, key, child) + json_object_object_foreach_len(jso, key, child) { userret = _json_c_visit(child, jso, key, NULL, userfunc, userarg); if (userret == JSON_C_VISIT_RETURN_POP) diff --git a/tests/test1.c b/tests/test1.c index f061961..66f5709 100644 --- a/tests/test1.c +++ b/tests/test1.c @@ -304,7 +304,7 @@ int main(int argc, char **argv) /*json_object_object_add(my_object, "arr", my_array);*/ printf("my_object=\n"); - json_object_object_foreach(my_object, key, val) + json_object_object_foreach_len(my_object, key, val) { putchar('\t'); fwrite(json_key_data(key), json_key_size(key), 1, stdout); diff --git a/tests/testReplaceExisting.c b/tests/testReplaceExisting.c index 70369d3..7e05d80 100644 --- a/tests/testReplaceExisting.c +++ b/tests/testReplaceExisting.c @@ -24,11 +24,10 @@ int main(int argc, char **argv) int orig_count = 0; json_object_object_foreach(my_object, key0, val0) { - printf("Key at index %d is [%s] %d", orig_count, json_key_data(key0), - (val0 == NULL)); - if (strcmp(json_key_data(key0), "deleteme") == 0) + printf("Key at index %d is [%s] %d", orig_count, key0, (val0 == NULL)); + if (strcmp(key0, "deleteme") == 0) { - json_object_object_del(my_object, json_key_data(key0)); + json_object_object_del(my_object, key0); printf(" (deleted)\n"); } else @@ -40,7 +39,7 @@ int main(int argc, char **argv) const struct json_key *original_key = NULL; orig_count = 0; - json_object_object_foreach(my_object, key, val) + json_object_object_foreach_len(my_object, key, val) { printf("Key at index %d is [%s] %d\n", orig_count, json_key_data(key), (val == NULL)); @@ -49,7 +48,7 @@ int main(int argc, char **argv) continue; printf("replacing value for key [%s]\n", json_key_data(key)); original_key = key; - json_object_object_add(my_object, json_key_data(key0), + json_object_object_add(my_object, json_key_data(key), json_object_new_string("zzz")); } @@ -57,7 +56,7 @@ int main(int argc, char **argv) int new_count = 0; int retval = 0; - json_object_object_foreach(my_object, key2, val2) + json_object_object_foreach_len(my_object, key2, val2) { printf("Key at index %d is [%s] %d\n", new_count, json_key_data(key2), (val2 == NULL)); diff --git a/tests/test_null_keys_add.c b/tests/test_null_keys_add.c index 51a21d8..06debb2 100644 --- a/tests/test_null_keys_add.c +++ b/tests/test_null_keys_add.c @@ -111,7 +111,7 @@ int main(void) printf("Have three keys, but don't have the right value in \"%s\" (error!)\n", toadd_key_printable); printf("Keys :\n"); - json_object_object_foreach(parsed, key, val) + json_object_object_foreach_len(parsed, key, val) { putchar('\"'); fwrite(json_key_data(key), json_key_size(key), 1, stdout); From 816b00c1c684f5ab997f7f01231cf5bfb922860d Mon Sep 17 00:00:00 2001 From: HexTheDragon Date: Sun, 18 Jul 2021 16:44:51 -0800 Subject: [PATCH 11/11] Add new functions so any old things still work --- json-c.sym | 2 + json_object.c | 139 +++++++++++++++++++++++++++++++++++++++-- json_object.h | 105 +++++++++++++++++++++++++++++-- tests/test_deep_copy.c | 9 +-- 4 files changed, 241 insertions(+), 14 deletions(-) diff --git a/json-c.sym b/json-c.sym index 2d6bd86..f12cf7b 100644 --- a/json-c.sym +++ b/json-c.sym @@ -176,4 +176,6 @@ JSONC_0.16 { json_object_object_get_len; json_object_object_get_ex_len; json_object_object_get_key; + json_c_shallow_copy_default_len; + json_object_deep_copy_len; } JSONC_0.15; diff --git a/json_object.c b/json_object.c index b56c14b..d4ed76f 100644 --- a/json_object.c +++ b/json_object.c @@ -1728,8 +1728,30 @@ static int json_object_copy_serializer_data(struct json_object *src, struct json * * This always returns -1 or 1. It will never return 2 since it does not copy the serializer. */ -int json_c_shallow_copy_default(json_object *src, json_object *parent, const struct json_key *key, +int json_c_shallow_copy_default(json_object *src, json_object *parent, const char *key, size_t index, json_object **dst) +{ + if (key == NULL) + { + return json_c_shallow_copy_default_len(src, parent, NULL, index, dst); + } + else + { + const struct json_key nkey = {strlen(key), {key}}; + return json_c_shallow_copy_default_len(src, parent, &nkey, index, dst); + } +} + +/** + * The default shallow copy implementation. Simply creates a new object of the same + * type but does *not* copy over _userdata nor retain any custom serializer. + * If custom serializers are in use, json_object_deep_copy() must be passed a shallow copy + * implementation that is aware of how to copy them. + * + * This always returns -1 or 1. It will never return 2 since it does not copy the serializer. + */ +int json_c_shallow_copy_default_len(json_object *src, json_object *parent, + const struct json_key *key, size_t index, json_object **dst) { switch (src->o_type) { @@ -1776,8 +1798,8 @@ int json_c_shallow_copy_default(json_object *src, json_object *parent, const str * Note: caller is responsible for freeing *dst if this fails and returns -1. */ static int json_object_deep_copy_recursive(struct json_object *src, struct json_object *parent, - const struct json_key *key_in_parent, - size_t index_in_parent, struct json_object **dst, + const char *key_in_parent, size_t index_in_parent, + struct json_object **dst, json_c_shallow_copy_fn *shallow_copy) { struct json_object_iter iter; @@ -1802,7 +1824,8 @@ static int json_object_deep_copy_recursive(struct json_object *src, struct json_ /* This handles the `json_type_null` case */ if (!iter.val) jso = NULL; - else if (json_object_deep_copy_recursive(iter.val, src, iter.key, UINT_MAX, + else if (json_object_deep_copy_recursive(iter.val, src, + json_key_data(iter.key), UINT_MAX, &jso, shallow_copy) < 0) { json_object_put(jso); @@ -1852,6 +1875,89 @@ static int json_object_deep_copy_recursive(struct json_object *src, struct json_ return 0; } +/* + * The actual guts of json_object_deep_copy(), with a few additional args + * needed so we can keep track of where we are within the object tree. + * + * Note: caller is responsible for freeing *dst if this fails and returns -1. + */ +static int json_object_deep_copy_recursive_len(struct json_object *src, struct json_object *parent, + const struct json_key *key_in_parent, + size_t index_in_parent, struct json_object **dst, + json_c_shallow_copy_fn_len *shallow_copy) +{ + struct json_object_iter iter; + size_t src_array_len, ii; + + int shallow_copy_rc = 0; + shallow_copy_rc = shallow_copy(src, parent, key_in_parent, index_in_parent, dst); + /* -1=error, 1=object created ok, 2=userdata set */ + if (shallow_copy_rc < 1) + { + errno = EINVAL; + return -1; + } + assert(*dst != NULL); + + switch (src->o_type) + { + case json_type_object: + json_object_object_foreachC(src, iter) + { + struct json_object *jso = NULL; + /* This handles the `json_type_null` case */ + if (!iter.val) + jso = NULL; + else if (json_object_deep_copy_recursive_len( + iter.val, src, iter.key, UINT_MAX, &jso, shallow_copy) < 0) + { + json_object_put(jso); + return -1; + } + + if (json_object_object_add_key(*dst, iter.key, jso, 0) < 0) + { + json_object_put(jso); + return -1; + } + } + break; + + case json_type_array: + src_array_len = json_object_array_length(src); + for (ii = 0; ii < src_array_len; ii++) + { + struct json_object *jso = NULL; + struct json_object *jso1 = json_object_array_get_idx(src, ii); + /* This handles the `json_type_null` case */ + if (!jso1) + jso = NULL; + else if (json_object_deep_copy_recursive_len(jso1, src, NULL, ii, &jso, + shallow_copy) < 0) + { + json_object_put(jso); + return -1; + } + + if (json_object_array_add(*dst, jso) < 0) + { + json_object_put(jso); + return -1; + } + } + break; + + default: + break; + /* else, nothing to do, shallow_copy already did. */ + } + + if (shallow_copy_rc != 2) + return json_object_copy_serializer_data(src, *dst); + + return 0; +} + int json_object_deep_copy(struct json_object *src, struct json_object **dst, json_c_shallow_copy_fn *shallow_copy) { @@ -1877,6 +1983,31 @@ int json_object_deep_copy(struct json_object *src, struct json_object **dst, return rc; } +int json_object_deep_copy_len(struct json_object *src, struct json_object **dst, + json_c_shallow_copy_fn_len *shallow_copy) +{ + int rc; + + /* Check if arguments are sane ; *dst must not point to a non-NULL object */ + if (!src || !dst || *dst) + { + errno = EINVAL; + return -1; + } + + if (shallow_copy == NULL) + shallow_copy = json_c_shallow_copy_default_len; + + rc = json_object_deep_copy_recursive_len(src, NULL, NULL, UINT_MAX, dst, shallow_copy); + if (rc < 0) + { + json_object_put(*dst); + *dst = NULL; + } + + return rc; +} + static void json_abort(const char *message) { if (message != NULL) diff --git a/json_object.h b/json_object.h index bc08554..b71cd50 100644 --- a/json_object.h +++ b/json_object.h @@ -1226,7 +1226,11 @@ JSON_EXPORT struct json_object *json_object_new_null(void); JSON_EXPORT int json_object_equal(struct json_object *obj1, struct json_object *obj2); /** - * Perform a shallow copy of src into *dst as part of an overall json_object_deep_copy(). + * @deprecated This type is provided for backward-compatability only. + * It is reccomended to use `json_c_shallow_copy_fn_len` instead, and + * properly handle copying of keys with embedded NULL ('\0') characters. + * + * @brief Perform a shallow copy of src into *dst as part of an overall json_object_deep_copy(). * * If src is part of a containing object or array, parent will be non-NULL, * and key or index will be provided. @@ -1237,13 +1241,51 @@ JSON_EXPORT int json_object_equal(struct json_object *obj1, struct json_object * * json_object_deep_copy that it should not attempt to use the standard userdata * copy function. * + * @param src The source object to be copied + * @param parent The the parent of the current object + * @param key The key that

src

has in

parent

, if

parent

+ * is an object + * @param index The index that

src

can be found at in

parent

, + * if

parent

is an array + * @param dst The destination object + * * @return On success 1 or 2, -1 on errors + * + * @since 0.13 + * + * @see json_c_shallow_copy_fn_len + * */ -typedef int(json_c_shallow_copy_fn)(json_object *src, json_object *parent, - const struct json_key *key, size_t index, json_object **dst); +typedef int(json_c_shallow_copy_fn)(json_object *src, json_object *parent, const char *key, + size_t index, json_object **dst); /** - * The default shallow copy implementation for use with json_object_deep_copy(). + * @brief Perform a shallow copy of src into *dst as part of an overall json_object_deep_copy(). + * + * If src is part of a containing object or array, parent will be non-NULL, + * and key or index will be provided. + * When shallow_copy is called *dst will be NULL, and must be non-NULL when it returns. + * src will never be NULL. + * + * If shallow_copy sets the serializer on an object, return 2 to indicate to + * json_object_deep_copy that it should not attempt to use the standard userdata + * copy function. + * + * @since 0.16 + * + * @return On success 1 or 2, -1 on errors + */ +typedef int(json_c_shallow_copy_fn_len)(json_object *src, json_object *parent, + const struct json_key *key, size_t index, + json_object **dst); + +/** + * @deprecated This type is provided for backward-compatability only. + * It is reccomended to use `json_c_shallow_copy_fn_len` instead, and + * properly handle copying of keys with embedded NULL ('\0') characters. + * + * @brief The default shallow copy implementation for use with json_object_deep_copy(). + * * This simply calls the appropriate json_object_new_() function and * copies over the serializer function (_to_json_string internal field of * the json_object structure) but not any _userdata or _user_delete values. @@ -1252,12 +1294,38 @@ typedef int(json_c_shallow_copy_fn)(json_object *src, json_object *parent, * your own custom serializer, you can call this first to create the new object * before customizing it with json_object_set_serializer(). * + * @since 0.13 + * * @return 1 on success, -1 on errors, but never 2. + * + * @see json_c_shallow_copy_fn_len */ JSON_EXPORT json_c_shallow_copy_fn json_c_shallow_copy_default; /** - * Copy the contents of the JSON object. + * @brief The default shallow copy implementation for use with json_object_deep_copy(). + * + * This simply calls the appropriate json_object_new_() function and + * copies over the serializer function (_to_json_string internal field of + * the json_object structure) but not any _userdata or _user_delete values. + * + * If you're writing a custom shallow_copy function, perhaps because you're using + * your own custom serializer, you can call this first to create the new object + * before customizing it with json_object_set_serializer(). + * + * @since 0.16 + * + * @return 1 on success, -1 on errors, but never 2. + */ +JSON_EXPORT json_c_shallow_copy_fn_len json_c_shallow_copy_default_len; + +/** + * @deprecated This function is provided for backward-compatability only. + * It is reccomended to use `json_object_deep_copy_len` instead, as it will + * properly handle copying of keys with embedded NULL ('\0') characters. + * + * @brief Copy the contents of the JSON object. + * * The destination object must be initialized to NULL, * to make sure this function won't overwrite an existing JSON object. * @@ -1271,13 +1339,38 @@ JSON_EXPORT json_c_shallow_copy_fn json_c_shallow_copy_default; * when custom serializers are in use. See also * json_object set_serializer. * + * @since 0.13 + * * @returns 0 if the copy went well, -1 if an error occured during copy * or if the destination pointer is non-NULL */ - JSON_EXPORT int json_object_deep_copy(struct json_object *src, struct json_object **dst, json_c_shallow_copy_fn *shallow_copy); +/** + * @brief Copy the contents of the JSON object. + * + * The destination object must be initialized to NULL, + * to make sure this function won't overwrite an existing JSON object. + * + * This does roughly the same thing as + * `json_tokener_parse(json_object_get_string(src))`. + * + * @param src source JSON object whose contents will be copied + * @param dst pointer to the destination object where the contents of `src`; + * make sure this pointer is initialized to NULL + * @param shallow_copy an optional function to copy individual objects, needed + * when custom serializers are in use. See also + * json_object set_serializer. + * + * @since 0.16 + * + * @returns 0 if the copy went well, -1 if an error occured during copy + * or if the destination pointer is non-NULL + */ +JSON_EXPORT int json_object_deep_copy_len(struct json_object *src, struct json_object **dst, + json_c_shallow_copy_fn_len *shallow_copy); + /* Json Object Keys */ /** diff --git a/tests/test_deep_copy.c b/tests/test_deep_copy.c index 421cb4f..b44daf5 100644 --- a/tests/test_deep_copy.c +++ b/tests/test_deep_copy.c @@ -92,15 +92,16 @@ int my_custom_serializer(struct json_object *jso, struct printbuf *pb, int level return 0; } -json_c_shallow_copy_fn my_shallow_copy; +json_c_shallow_copy_fn_len my_shallow_copy; int my_shallow_copy(json_object *src, json_object *parent, const struct json_key *key, size_t index, json_object **dst) { int rc; - rc = json_c_shallow_copy_default(src, parent, key, index, dst); + rc = json_c_shallow_copy_default_len(src, parent, key, index, dst); if (rc < 0) return rc; - if (key != NULL && strcmp(json_key_data(key), "with_serializer") == 0) + if (key != NULL && json_key_size(key) == 15 && + strcmp(json_key_data(key), "with_serializer") == 0) { printf("CALLED: my_shallow_copy on with_serializer object\n"); void *userdata = json_object_get_userdata(src); @@ -199,7 +200,7 @@ int main(int argc, char **argv) dst1 = NULL; /* With a custom serializer in use, a custom shallow_copy function must also be used */ assert(-1 == json_object_deep_copy(src1, &dst1, NULL)); - assert(0 == json_object_deep_copy(src1, &dst1, my_shallow_copy)); + assert(0 == json_object_deep_copy_len(src1, &dst1, my_shallow_copy)); json_object *dest_with_serializer = json_object_object_get(dst1, "with_serializer"); assert(dest_with_serializer != NULL);