diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index 51cf405..27d9e61 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -56,6 +56,7 @@ #endif #include +#include #ifndef WOLFSENTRY_NO_ALLOCA #include #endif @@ -255,6 +256,27 @@ json_value_type(const JSON_VALUE* v) return (JSON_VALUE_TYPE)(v->data.data_bytes[0] & TYPE_MASK); } +static uint64_t json_int_magnitude(int64_t x) +{ + return (x < 0) ? (~(uint64_t)x + 1U) : (uint64_t)x; +} + +/* An integer is exactly representable in a binary floating type with + * mantissa_bits of precision when its significant bits (ignoring trailing + * zeros) fit in that precision. */ +static int json_int_fits_mantissa(uint64_t u, int mantissa_bits) +{ + int bits = 0; + + while((u != 0) && ((u & 1U) == 0)) + u >>= 1; + while(u != 0) { + ++bits; + u >>= 1; + } + return bits <= mantissa_bits; +} + WOLFSENTRY_API int json_value_is_compatible(const JSON_VALUE* v, JSON_VALUE_TYPE type) { @@ -265,39 +287,43 @@ json_value_is_compatible(const JSON_VALUE* v, JSON_VALUE_TYPE type) * long as the conversion does not loose much information. */ switch(json_value_type(v)) { case JSON_VALUE_INT32: - return (type == JSON_VALUE_INT64 || type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_INT64 || type == JSON_VALUE_DOUBLE) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa(json_int_magnitude(json_value_int32(v)), FLT_MANT_DIG)) || (type == JSON_VALUE_UINT32 && json_value_int32(v) >= 0) || (type == JSON_VALUE_UINT64 && json_value_int32(v) >= 0); case JSON_VALUE_UINT32: - return (type == JSON_VALUE_INT64 || type == JSON_VALUE_UINT64 || type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_INT64 || type == JSON_VALUE_UINT64 || type == JSON_VALUE_DOUBLE) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa((uint64_t)json_value_uint32(v), FLT_MANT_DIG)) || (type == JSON_VALUE_INT32 && json_value_uint32(v) <= INT32_MAX); case JSON_VALUE_INT64: - return (type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_DOUBLE && json_int_fits_mantissa(json_int_magnitude(json_value_int64(v)), DBL_MANT_DIG)) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa(json_int_magnitude(json_value_int64(v)), FLT_MANT_DIG)) || (type == JSON_VALUE_INT32 && json_value_int64(v) >= INT32_MIN && json_value_int64(v) <= INT32_MAX) || (type == JSON_VALUE_UINT32 && json_value_int64(v) >= 0 && json_value_int64(v) <= UINT32_MAX) || (type == JSON_VALUE_UINT64 && json_value_int64(v) >= 0); case JSON_VALUE_UINT64: - return (type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_DOUBLE && json_int_fits_mantissa(json_value_uint64(v), DBL_MANT_DIG)) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa(json_value_uint64(v), FLT_MANT_DIG)) || (type == JSON_VALUE_INT32 && json_value_uint64(v) <= INT32_MAX) || (type == JSON_VALUE_UINT32 && json_value_uint64(v) <= UINT32_MAX) || (type == JSON_VALUE_INT64 && json_value_uint64(v) <= INT64_MAX); case JSON_VALUE_FLOAT: return (type == JSON_VALUE_DOUBLE) || - (type == JSON_VALUE_INT32 && json_value_float(v) == (float)json_value_int32(v)) || - (type == JSON_VALUE_UINT32 && json_value_float(v) == (float)json_value_uint32(v)) || - (type == JSON_VALUE_INT64 && json_value_float(v) == (float)json_value_int64(v)) || - (type == JSON_VALUE_UINT64 && json_value_float(v) == (float)json_value_uint64(v)); + (type == JSON_VALUE_INT32 && (double)json_value_float(v) >= -2147483648.0 && (double)json_value_float(v) <= 2147483647.0 && json_value_float(v) == (float)json_value_int32(v)) || + (type == JSON_VALUE_UINT32 && (double)json_value_float(v) >= 0.0 && (double)json_value_float(v) <= 4294967295.0 && json_value_float(v) == (float)json_value_uint32(v)) || + (type == JSON_VALUE_INT64 && (double)json_value_float(v) >= -9223372036854775808.0 && (double)json_value_float(v) < 9223372036854775808.0 && json_value_float(v) == (float)json_value_int64(v)) || + (type == JSON_VALUE_UINT64 && (double)json_value_float(v) >= 0.0 && (double)json_value_float(v) < 18446744073709551616.0 && json_value_float(v) == (float)json_value_uint64(v)); case JSON_VALUE_DOUBLE: - return (type == JSON_VALUE_FLOAT) || - (type == JSON_VALUE_INT32 && json_value_double(v) == (double)json_value_int32(v)) || - (type == JSON_VALUE_UINT32 && json_value_double(v) == (double)json_value_uint32(v)) || - (type == JSON_VALUE_INT64 && json_value_double(v) == (double)json_value_int64(v)) || - (type == JSON_VALUE_UINT64 && json_value_double(v) == (double)json_value_uint64(v)); + return (type == JSON_VALUE_FLOAT && json_value_double(v) >= -(double)FLT_MAX && json_value_double(v) <= (double)FLT_MAX && (double)(float)json_value_double(v) == json_value_double(v)) || + (type == JSON_VALUE_INT32 && json_value_double(v) >= -2147483648.0 && json_value_double(v) <= 2147483647.0 && json_value_double(v) == (double)json_value_int32(v)) || + (type == JSON_VALUE_UINT32 && json_value_double(v) >= 0.0 && json_value_double(v) <= 4294967295.0 && json_value_double(v) == (double)json_value_uint32(v)) || + (type == JSON_VALUE_INT64 && json_value_double(v) >= -9223372036854775808.0 && json_value_double(v) < 9223372036854775808.0 && json_value_double(v) == (double)json_value_int64(v)) || + (type == JSON_VALUE_UINT64 && json_value_double(v) >= 0.0 && json_value_double(v) < 18446744073709551616.0 && json_value_double(v) == (double)json_value_uint64(v)); default: break; @@ -320,30 +346,67 @@ json_value_path(JSON_VALUE* root, const char* path) { const unsigned char* token_beg = (const unsigned char *)path; const unsigned char* token_end; + const unsigned char* p; JSON_VALUE* v = root; + size_t path_index; + size_t arr_size; + int negative; + int have_digit; while(1) { token_end = token_beg; while(*token_end != '\0' && *token_end != '/') token_end++; - if(token_end - token_beg > 2 && token_beg[0] == '[' && token_end[-1] == ']') { - size_t path_index = 0; + p = token_beg; + + /* Optional leading dictionary key, up to the first '[' or token end. */ + if(p < token_end && *p != '[') { + const unsigned char* key_end = p; + while(key_end < token_end && *key_end != '[') + key_end++; + v = json_value_dict_get_(v, p, (size_t)(key_end - p)); + if(v == NULL) + return NULL; + p = key_end; + } - token_beg++; - while('0' <= *token_beg && *token_beg <= '9') { - path_index = (path_index * 10U) + (*token_beg - (unsigned)'0'); - token_beg++; + /* Zero or more attached array indexes "[n]", where n may be negative + * (relative to the array length). */ + while(p < token_end && *p == '[') { + p++; + negative = 0; + if(*p == '-') { + negative = 1; + p++; } - if(*token_beg != ']') + path_index = 0; + have_digit = 0; + while('0' <= *p && *p <= '9') { + size_t digit = (size_t)(*p - (unsigned char)'0'); + if(path_index > (SIZE_MAX - digit) / 10U) + return NULL; + path_index = (path_index * 10U) + digit; + have_digit = 1; + p++; + } + if((! have_digit) || (*p != ']')) return NULL; + p++; + if(negative) { + arr_size = json_value_array_size(v); + if((path_index == 0) || (path_index > arr_size)) + return NULL; + path_index = arr_size - path_index; + } v = json_value_array_get(v, path_index); - } else if(token_end - token_beg > 0) { - v = json_value_dict_get_(v, token_beg, (size_t)(token_end - token_beg)); + if(v == NULL) + return NULL; } - if(v == NULL) + /* The token must be fully consumed; reject trailing bytes. */ + if(p != token_end) return NULL; if(*token_end == '\0') @@ -881,6 +944,15 @@ json_value_array_realloc( { JSON_VALUE* json_value_buf; + /* Shrinking to zero: free the buffer directly rather than via realloc(p, 0), + * which is allowed to free and return NULL and would leave a dangling buffer. */ + if(alloc == 0) { + free(a->json_value_buf); + a->json_value_buf = NULL; + a->alloc = 0; + return 0; + } + json_value_buf = (JSON_VALUE*) realloc(a->json_value_buf, alloc * sizeof(JSON_VALUE)); if(json_value_buf == NULL) return -1; @@ -945,16 +1017,21 @@ json_value_array_insert( JSON_VALUE* v, size_t index) { ARRAY* a = json_value_array_payload(v); + size_t new_alloc; if(a == NULL || index > a->size) return NULL; if(a->size >= a->alloc) { + new_alloc = (a->alloc > 0) ? a->alloc * 2 : 1; + /* a wrapped doubling must not reach the shrink-to-zero path */ + if(new_alloc <= a->size) + return NULL; if(json_value_array_realloc( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif - a, (a->alloc > 0) ? a->alloc * 2 : 1) != 0) + a, new_alloc) != 0) return NULL; } @@ -990,12 +1067,16 @@ json_value_array_remove_range( { ARRAY* a = json_value_array_payload(v); size_t i; + size_t end; int ret; - if(a == NULL || index + count > a->size) + /* Validate without adding, so index + count cannot wrap. */ + if(a == NULL || index > a->size || count > a->size - index) return -1; - for(i = index; i < index + count; i++) { + end = index + count; + + for(i = index; i < end; i++) { ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), @@ -1005,9 +1086,9 @@ json_value_array_remove_range( return ret; } - if(index + count < a->size) { - memmove(a->json_value_buf + index, a->json_value_buf + index + count, - (a->size - (index + count)) * sizeof(JSON_VALUE)); + if(end < a->size) { + memmove(a->json_value_buf + index, a->json_value_buf + end, + (a->size - end) * sizeof(JSON_VALUE)); } a->size -= count; @@ -1556,6 +1637,7 @@ json_value_dict_remove_( int path_len = 0; int cmp; int ret; + int ret2; if (node == NULL) return -1; @@ -1673,24 +1755,24 @@ json_value_dict_remove_( else d->order_tail = node->order_prev; } + /* The node is already unlinked, so finalize both members best-effort, then + * always free it and decrement the size to keep the dictionary consistent. */ ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->key); - if (ret < 0) - return ret; - ret = json_value_fini( + ret2 = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->json_value); - if (ret < 0) - return ret; + if (ret == 0) + ret = ret2; free(node); d->size--; - return 0; + return ret; } WOLFSENTRY_API int @@ -1779,11 +1861,17 @@ json_value_dict_clean( if(d == NULL) return 0; - stack = (RBTREE **)malloc(rbtree_stack_size_needed(d)); - if (stack == NULL) - return JSON_ERR_OUTOFMEMORY; + /* An empty tree needs no stack; a conforming allocator may return NULL for a + * zero-size request, so do not treat that as an error. */ + if(d->root != NULL) { + stack = (RBTREE **)malloc(rbtree_stack_size_needed(d)); + if (stack == NULL) + return JSON_ERR_OUTOFMEMORY; + } else { + stack = NULL; + } - stack_size = json_value_dict_leftmost_path(stack, d->root); + stack_size = (stack != NULL) ? json_value_dict_leftmost_path(stack, d->root) : 0; while(stack_size > 0) { node = stack[--stack_size]; @@ -1795,7 +1883,6 @@ json_value_dict_clean( #endif &node->key); if (ret < 0) { - free(node); free((void *)stack); return ret; } @@ -1805,10 +1892,25 @@ json_value_dict_clean( #endif &node->json_value); if (ret < 0) { - free(node); free((void *)stack); return ret; } + /* unlink before freeing so a failed clean leaves only live nodes for a + * retry; size is kept, as it still bounds the retry's stack */ + if(stack_size > 0) + stack[stack_size - 1]->left = right; + else + d->root = right; + if(v->data.data_bytes[0] & HAS_ORDERLIST) { + if(node->order_prev != NULL) + node->order_prev->order_next = node->order_next; + else + d->order_head = node->order_next; + if(node->order_next != NULL) + node->order_next->order_prev = node->order_prev; + else + d->order_tail = node->order_prev; + } free(node); stack_size += json_value_dict_leftmost_path(stack + stack_size, right); @@ -1876,38 +1978,47 @@ json_value_clone(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allo const ARRAY* src_a = json_value_array_payload((JSON_VALUE *)node); size_t n = json_value_array_size(node); const JSON_VALUE* src_values = json_value_array_get_all(node); - ARRAY* dest_a; - JSON_VALUE* dest_values; + ARRAY* dest_a = NULL; + JSON_VALUE* dest_values = NULL; size_t i; ret = json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), clone); - if (ret < 0) - break; - dest_a = json_value_array_payload(clone); - if (! dest_a) { - ret = JSON_ERR_INTERNAL; - break; + if (ret == 0) { + dest_a = json_value_array_payload(clone); + if (dest_a == NULL) + ret = JSON_ERR_INTERNAL; } - ret = json_value_array_realloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), - dest_a, src_a->alloc); - if (ret < 0) - break; + /* Skip the element buffer entirely for an empty array; a zero-size + * allocation may legitimately return NULL. */ + if ((ret == 0) && (src_a->alloc > 0)) + ret = json_value_array_realloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), + dest_a, src_a->alloc); - dest_values = json_value_array_get_all(clone); - if (! dest_values) { - ret = JSON_ERR_INTERNAL; - break; + if (ret == 0) { + dest_values = json_value_array_get_all(clone); + if ((n > 0) && (dest_values == NULL)) + ret = JSON_ERR_INTERNAL; } - for(i = 0; i < n; i++) { - ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src_values[i], &dest_values[i]); - if (ret < 0) - break; + if (ret == 0) { + for(i = 0; i < n; i++) { + json_value_init_null(&dest_values[i]); + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src_values[i], &dest_values[i]); + if (ret < 0) + break; + dest_a->size = i + 1; + } } - dest_a->size = src_a->size; + /* On any failure finalize the clone so the initialized prefix and + * the array allocation are released, not leaked. */ + if (ret < 0) { + int ret2 = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), clone); + if (ret2 < 0) + WOLFSENTRY_WARN("json_value_fini: %s\n", json_error_str(ret2)); + } break; } @@ -1940,11 +2051,16 @@ json_value_clone(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allo } } else { int stack_size; - RBTREE **stack = (RBTREE **)malloc(rbtree_stack_size_needed(src_dict)); /* put this on the heap to avoid runaway growth of stack on deep JSON trees. */ - if (! stack) { + RBTREE **stack = NULL; + /* put the stack on the heap to avoid runaway growth of the C + * stack on deep JSON trees; an empty tree needs none, and a + * zero-size allocation may legitimately return NULL. */ + if (src_dict->root != NULL) + stack = (RBTREE **)malloc(rbtree_stack_size_needed(src_dict)); + if ((src_dict->root != NULL) && (stack == NULL)) { ret = JSON_ERR_OUTOFMEMORY; } else { - stack_size = json_value_dict_leftmost_path(stack, src_dict->root); + stack_size = (stack != NULL) ? json_value_dict_leftmost_path(stack, src_dict->root) : 0; while(stack_size > 0) { src_dict_node = stack[--stack_size]; diff --git a/tests/filter-clang-analyzer.py b/tests/filter-clang-analyzer.py index f4e199f..72bdbea 100644 --- a/tests/filter-clang-analyzer.py +++ b/tests/filter-clang-analyzer.py @@ -8,9 +8,9 @@ ALLOWED_RESULTS = { - ("core.uninitialized.Assign", "src/json/centijson_value.c", 1285), - ("core.NullDereference", "src/json/centijson_value.c", 1483), - ("core.NullDereference", "src/json/centijson_value.c", 1520), + ("core.uninitialized.Assign", "src/json/centijson_value.c", 1366), + ("core.NullDereference", "src/json/centijson_value.c", 1564), + ("core.NullDereference", "src/json/centijson_value.c", 1601), } diff --git a/tests/unittests.c b/tests/unittests.c index 7a75686..08cd566 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5466,6 +5466,587 @@ static int test_json(const char *fname, const char *extra_fname) { WOLFSENTRY_RETURN_OK; } +#ifdef WOLFSENTRY_HAVE_JSON_DOM + +/* Regression coverage for the centijson JSON_VALUE (DOM) public API. Uses a + * controllable test allocator so the error paths can be exercised through the + * public interface. */ + +static size_t test_cjv_live = 0; /* count of live allocations */ +static size_t test_cjv_fail_big = 0; /* if nonzero, fail allocations >= this size */ +static int test_cjv_zero_null = 0; /* if set, zero-size requests return NULL */ +static long test_cjv_budget = -1; /* if >= 0, allow this many mallocs then fail */ + +static void test_cjv_reset(void) { + test_cjv_live = 0; + test_cjv_fail_big = 0; + test_cjv_zero_null = 0; + test_cjv_budget = -1; +} + +#ifdef WOLFSENTRY_THREADSAFE +static void *test_cjv_malloc(void *context, struct wolfsentry_thread_context *thread, size_t size) +#else +static void *test_cjv_malloc(void *context, size_t size) +#endif +{ + void *p; + (void)context; +#ifdef WOLFSENTRY_THREADSAFE + (void)thread; +#endif + if (test_cjv_zero_null && (size == 0)) + return NULL; + if ((test_cjv_fail_big != 0) && (size >= test_cjv_fail_big)) + return NULL; + if (test_cjv_budget >= 0) { + if (test_cjv_budget == 0) + return NULL; + --test_cjv_budget; + } + p = malloc(size); + if (p != NULL) + ++test_cjv_live; + return p; +} + +#ifdef WOLFSENTRY_THREADSAFE +static void test_cjv_free(void *context, struct wolfsentry_thread_context *thread, void *ptr) +#else +static void test_cjv_free(void *context, void *ptr) +#endif +{ + (void)context; +#ifdef WOLFSENTRY_THREADSAFE + (void)thread; +#endif + if (ptr != NULL) + --test_cjv_live; + free(ptr); +} + +#ifdef WOLFSENTRY_THREADSAFE +static void *test_cjv_realloc(void *context, struct wolfsentry_thread_context *thread, void *ptr, size_t size) +#else +static void *test_cjv_realloc(void *context, void *ptr, size_t size) +#endif +{ + void *p; + int was_null; + (void)context; +#ifdef WOLFSENTRY_THREADSAFE + (void)thread; +#endif + if (test_cjv_zero_null && (size == 0)) { + if (ptr != NULL) + --test_cjv_live; + free(ptr); + return NULL; + } + if ((test_cjv_fail_big != 0) && (size >= test_cjv_fail_big)) + return NULL; + was_null = (ptr == NULL); + p = realloc(ptr, size); + if (p != NULL) { + if (was_null) + ++test_cjv_live; + } else if ((size == 0) && (! was_null)) { + --test_cjv_live; + } + return p; +} + +static int test_cjvalue_path(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE root; + JSON_VALUE *list; + JSON_VALUE *elem; + JSON_VALUE *found; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root) < 0); + + list = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root, (const unsigned char *)"list", 4); + WOLFSENTRY_EXIT_ON_TRUE(list == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list) < 0); + + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, 10) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, 20) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, 30) < 0); + + /* Attached array index (key followed immediately by a bracket). */ + found = json_value_path(&root, "list[0]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 10); + + found = json_value_path(&root, "list[2]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 30); + + /* Slash-separated index is a documented equivalent of the attached form. */ + found = json_value_path(&root, "list/[1]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 20); + + /* Negative indexes are relative to the array length. */ + found = json_value_path(&root, "list[-1]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 30); + + found = json_value_path(&root, "list[-3]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 10); + + /* Out-of-range indexes resolve to NULL. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list[3]") == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list[-4]") == NULL); + + /* Trailing bytes after a bracket index must be rejected. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list/[0]garbage]") == NULL); + + /* An index that overflows size_t must be rejected, not wrapped. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list/[18446744073709551616]") == NULL); + + /* An empty path returns the root. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "") == &root); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE arr; + JSON_VALUE *elem; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + for (i = 0; i < 3; ++i) { + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, i) < 0); + } + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 3); + + /* index + count must not wrap past the array size. */ + ret = json_value_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr, 3, SIZE_MAX); + WOLFSENTRY_EXIT_ON_FALSE(ret == -1); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 3); + + /* a legitimate removal still works. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr, 1, 1) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 2); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(&arr, 1)) == 2); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + return 0; +} + +static int test_cjvalue_clone_array_partial(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE src; + JSON_VALUE clone; + JSON_VALUE *elem; + JSON_VALUE *inner; + unsigned char small_str[50]; + unsigned char big_str[256]; + int i; + int ret; + + memset(small_str, 'a', sizeof small_str); + memset(big_str, 'b', sizeof big_str); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_string_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, small_str, sizeof small_str) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_string_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, big_str, sizeof big_str) < 0); + + json_value_init_null(&clone); + + /* Force the clone of the second (large) element to fail partway. */ + test_cjv_fail_big = 200; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src, &clone); + test_cjv_fail_big = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + /* The failed clone must finalize the already-cloned prefix, leaking nothing. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + /* Also exercise failure on a partially-built nested aggregate element: a + * nested array whose element buffer allocation fails after its payload was + * allocated must not leak that payload. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + inner = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src); + WOLFSENTRY_EXIT_ON_TRUE(inner == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), inner) < 0); + for (i = 0; i < 16; ++i) { + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), inner); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, i) < 0); + inner = json_value_array_get(&src, 0); + WOLFSENTRY_EXIT_ON_TRUE(inner == NULL); + } + + json_value_init_null(&clone); + /* The clone of the 16-slot inner array needs a 256-byte element buffer, + * which fails after the inner array payload has been allocated. */ + test_cjv_fail_big = 200; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src, &clone); + test_cjv_fail_big = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_clone_empty_array(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE src; + JSON_VALUE clone; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + json_value_init_null(&clone); + + /* With an allocator that returns NULL for zero-size requests, cloning an + * empty array must still succeed (it needs no element storage). */ + test_cjv_zero_null = 1; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src, &clone); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret >= 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&clone) == JSON_VALUE_ARRAY); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&clone) == 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &clone) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_compatible_lossy(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE v; + + /* A finite double outside the float range is not float-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1e308) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A small double is float-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 2.0) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^24 + 1 is not exactly representable in a float. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int64_t)16777217) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^24 + 2 is exactly representable in a float and must stay compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int64_t)16777218) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A small integer is float-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int32_t)1000) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^53 + 1 is not exactly representable in a double. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (((uint64_t)1 << 53) + 1)) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^53 + 2 is exactly representable in a double and must stay compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (((uint64_t)1 << 53) + 2)) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A small integer is double-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int32_t)5) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + return 0; +} + +static int test_cjvalue_compatible_float_to_int(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE v; + + /* Out-of-range float/double values must not be treated as integer-compatible, + * and the predicate must not perform an out-of-range floating-to-integer cast. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_float(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1e30F) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_UINT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1e300) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_UINT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* An in-range integral float is integer-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_float(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 42.0F) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A non-integral double is not integer-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1.5) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + return 0; +} + +static int test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE nested_copy; + JSON_VALUE *nested; + JSON_VALUE *slot; + char keybuf[8]; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + for (i = 0; i < 4; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); + } + + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"b", 1); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)1) < 0); + + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&outer) == 2); + + /* Keep an alias to the nested dictionary so the test can reclaim it after + * removal frees the owning node (see below). */ + nested = json_value_dict_get(&outer, (const unsigned char *)"a"); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + memcpy(&nested_copy, nested, sizeof nested_copy); + + /* Force value finalization (nested dictionary cleanup) to fail during removal. */ + test_cjv_budget = 0; + ret = json_value_dict_remove_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + /* The removed node must not remain counted in the dictionary. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&outer) == 1); + + /* Reclaim the nested dictionary, whose finalization the fault interrupted. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &nested_copy) < 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_dict_clean_retry_later_node(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE *nested; + JSON_VALUE *slot; + char keybuf[8]; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + for (i = 0; i < 3; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); + } + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"zz", 2); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)"in", 2); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, 7) < 0); + + /* the earlier keys are freed before the nested dictionary's stack + * allocation fails on the last key. */ + test_cjv_budget = 1; + ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + /* a retry must not reach the nodes freed before the failure */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE *nested; + JSON_VALUE *slot; + char keybuf[8]; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + for (i = 0; i < 4; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); + } + + /* First cleanup allocates the outer traversal stack, then fails on the + * nested dictionary's stack allocation. */ + test_cjv_budget = 1; + ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + /* A retry must succeed: no node may have been freed while still linked. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_empty_dict(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE d; + JSON_VALUE clone; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &d) < 0); + json_value_init_null(&clone); + + /* Cloning an empty dictionary must not require a zero-size allocation. */ + test_cjv_zero_null = 1; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &d, &clone); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret >= 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&clone) == JSON_VALUE_DICT); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &clone) < 0); + + /* Cleaning up an empty dictionary must not require a zero-size allocation. */ + test_cjv_zero_null = 1; + ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &d); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret >= 0); + + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_array_shrink_to_zero(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE arr; + JSON_VALUE *elem; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, (int32_t)7) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 1); + + /* Model an allocator for which realloc(ptr, 0) frees and returns NULL. + * Removing the last element shrinks the backing store to zero capacity; the + * array must stay usable (no dangling buffer) and finalize cleanly. */ + test_cjv_zero_null = 1; + WOLFSENTRY_EXIT_ON_TRUE(json_value_array_remove(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr, 0) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 0); + + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, (int32_t)9) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(&arr, 0)) == 9); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static wolfsentry_errcode_t test_cjvalue_fixes(void) { + struct wolfsentry_allocator test_allocator; + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + memset(&test_allocator, 0, sizeof test_allocator); + test_allocator.malloc = test_cjv_malloc; + test_allocator.free = test_cjv_free; + test_allocator.realloc = test_cjv_realloc; + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_path(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_clone_array_partial(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_clone_empty_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_compatible_lossy(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_compatible_float_to_int(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry_later_node(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_empty_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_array_shrink_to_zero(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -5784,6 +6365,13 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json failed for " TEST_NUMERIC_JSON_CONFIG_PATH ", " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } +#ifdef WOLFSENTRY_HAVE_JSON_DOM + ret = test_cjvalue_fixes(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_cjvalue_fixes failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif #endif #ifdef TEST_JSON_CORPUS