Guard deep structural equality and comparison recursion (#3539)
jv_equal and jv_cmp overflows the C stack on deeply nested input. Cap recursion at 10000 with -1 / INT_MIN sentinels; operators that compose user expressions surface this as "Equality check too deep" / "Comparison too deep". Fixes CVE-2026-47770.
This commit is contained in:
1 parent
f58787c418
commit
7122866869
4 files changed
+180
-28
No files matched your search
+33
-3
@@ -297,7 +297,15 @@ jv binop_minus(jv a, jv b) {
|
|||||||
jv_array_foreach(a, i, x) {
|
jv_array_foreach(a, i, x) {
|
||||||
int include = 1;
|
int include = 1;
|
||||||
jv_array_foreach(b, j, y) {
|
jv_array_foreach(b, j, y) {
|
||||||
if (jv_equal(jv_copy(x), y)) {
|
int equal = jv_equal(jv_copy(x), y);
|
||||||
|
if (equal < 0) {
|
||||||
|
jv_free(out);
|
||||||
|
jv_free(x);
|
||||||
|
jv_free(a);
|
||||||
|
jv_free(b);
|
||||||
|
return jv_invalid_with_msg(jv_string("Equality check too deep"));
|
||||||
|
}
|
||||||
|
if (equal) {
|
||||||
include = 0;
|
include = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -381,11 +389,17 @@ jv binop_mod(jv a, jv b) {
|
|||||||
#undef dtoi
|
#undef dtoi
|
||||||
|
|
||||||
jv binop_equal(jv a, jv b) {
|
jv binop_equal(jv a, jv b) {
|
||||||
return jv_bool(jv_equal(a, b));
|
int r = jv_equal(a, b);
|
||||||
|
if (r < 0)
|
||||||
|
return jv_invalid_with_msg(jv_string("Equality check too deep"));
|
||||||
|
return jv_bool(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
jv binop_notequal(jv a, jv b) {
|
jv binop_notequal(jv a, jv b) {
|
||||||
return jv_bool(!jv_equal(a, b));
|
int r = jv_equal(a, b);
|
||||||
|
if (r < 0)
|
||||||
|
return jv_invalid_with_msg(jv_string("Equality check too deep"));
|
||||||
|
return jv_bool(!r);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum cmp_op {
|
enum cmp_op {
|
||||||
@@ -397,6 +411,8 @@ enum cmp_op {
|
|||||||
|
|
||||||
static jv order_cmp(jv a, jv b, enum cmp_op op) {
|
static jv order_cmp(jv a, jv b, enum cmp_op op) {
|
||||||
int r = jv_cmp(a, b);
|
int r = jv_cmp(a, b);
|
||||||
|
if (r == INT_MIN)
|
||||||
|
return jv_invalid_with_msg(jv_string("Comparison too deep"));
|
||||||
return jv_bool((op == CMP_OP_LESS && r < 0) ||
|
return jv_bool((op == CMP_OP_LESS && r < 0) ||
|
||||||
(op == CMP_OP_LESSEQ && r <= 0) ||
|
(op == CMP_OP_LESSEQ && r <= 0) ||
|
||||||
(op == CMP_OP_GREATEREQ && r >= 0) ||
|
(op == CMP_OP_GREATEREQ && r >= 0) ||
|
||||||
@@ -868,6 +884,12 @@ static jv f_bsearch(jq_state *jq, jv input, jv target) {
|
|||||||
while (start < end) {
|
while (start < end) {
|
||||||
int mid = start + (end - start) / 2;
|
int mid = start + (end - start) / 2;
|
||||||
int result = jv_cmp(jv_copy(target), jv_array_get(jv_copy(input), mid));
|
int result = jv_cmp(jv_copy(target), jv_array_get(jv_copy(input), mid));
|
||||||
|
if (result == INT_MIN) {
|
||||||
|
jv_free(answer);
|
||||||
|
jv_free(input);
|
||||||
|
jv_free(target);
|
||||||
|
return jv_invalid_with_msg(jv_string("Comparison too deep"));
|
||||||
|
}
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
answer = jv_number(mid);
|
answer = jv_number(mid);
|
||||||
break;
|
break;
|
||||||
@@ -1159,6 +1181,14 @@ static jv minmax_by(jv values, jv keys, int is_min) {
|
|||||||
for (int i=1; i<jv_array_length(jv_copy(values)); i++) {
|
for (int i=1; i<jv_array_length(jv_copy(values)); i++) {
|
||||||
jv item = jv_array_get(jv_copy(keys), i);
|
jv item = jv_array_get(jv_copy(keys), i);
|
||||||
int cmp = jv_cmp(jv_copy(item), jv_copy(retkey));
|
int cmp = jv_cmp(jv_copy(item), jv_copy(retkey));
|
||||||
|
if (cmp == INT_MIN) {
|
||||||
|
jv_free(item);
|
||||||
|
jv_free(values);
|
||||||
|
jv_free(keys);
|
||||||
|
jv_free(retkey);
|
||||||
|
jv_free(ret);
|
||||||
|
return jv_invalid_with_msg(jv_string("Comparison too deep"));
|
||||||
|
}
|
||||||
if ((cmp < 0) == (is_min == 1)) {
|
if ((cmp < 0) == (is_min == 1)) {
|
||||||
jv_free(retkey);
|
jv_free(retkey);
|
||||||
retkey = item;
|
retkey = item;
|
||||||
|
|||||||
@@ -896,16 +896,20 @@ static jv* jvp_array_write(jv* a, int i) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int jvp_array_equal(jv a, jv b) {
|
static int jvp_equal(jv a, jv b, int depth);
|
||||||
|
|
||||||
|
static int jvp_array_equal(jv a, jv b, int depth) {
|
||||||
if (jvp_array_length(a) != jvp_array_length(b))
|
if (jvp_array_length(a) != jvp_array_length(b))
|
||||||
return 0;
|
return 0;
|
||||||
if (jvp_array_ptr(a) == jvp_array_ptr(b) &&
|
if (jvp_array_ptr(a) == jvp_array_ptr(b) &&
|
||||||
jvp_array_offset(a) == jvp_array_offset(b))
|
jvp_array_offset(a) == jvp_array_offset(b))
|
||||||
return 1;
|
return 1;
|
||||||
for (int i=0; i<jvp_array_length(a); i++) {
|
for (int i=0; i<jvp_array_length(a); i++) {
|
||||||
if (!jv_equal(jv_copy(*jvp_array_read(a, i)),
|
int r = jvp_equal(jv_copy(*jvp_array_read(a, i)),
|
||||||
jv_copy(*jvp_array_read(b, i))))
|
jv_copy(*jvp_array_read(b, i)),
|
||||||
return 0;
|
depth);
|
||||||
|
if (r <= 0)
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -1055,7 +1059,14 @@ jv jv_array_indexes(jv a, jv b) {
|
|||||||
int alen = jv_array_length(jv_copy(a));
|
int alen = jv_array_length(jv_copy(a));
|
||||||
for (int ai = 0; ai < alen; ++ai) {
|
for (int ai = 0; ai < alen; ++ai) {
|
||||||
jv_array_foreach(b, bi, belem) {
|
jv_array_foreach(b, bi, belem) {
|
||||||
if (!jv_equal(jv_array_get(jv_copy(a), ai + bi), belem))
|
int equal = jv_equal(jv_array_get(jv_copy(a), ai + bi), belem);
|
||||||
|
if (equal < 0) {
|
||||||
|
jv_free(res);
|
||||||
|
jv_free(a);
|
||||||
|
jv_free(b);
|
||||||
|
return jv_invalid_with_msg(jv_string("Equality check too deep"));
|
||||||
|
}
|
||||||
|
if (!equal)
|
||||||
idx = -1;
|
idx = -1;
|
||||||
else if (bi == 0 && idx == -1)
|
else if (bi == 0 && idx == -1)
|
||||||
idx = ai;
|
idx = ai;
|
||||||
@@ -1799,7 +1810,7 @@ static int jvp_object_length(jv object) {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int jvp_object_equal(jv o1, jv o2) {
|
static int jvp_object_equal(jv o1, jv o2, int depth) {
|
||||||
int len2 = jvp_object_length(o2);
|
int len2 = jvp_object_length(o2);
|
||||||
int len1 = 0;
|
int len1 = 0;
|
||||||
for (int i=0; i<jvp_object_size(o1); i++) {
|
for (int i=0; i<jvp_object_size(o1); i++) {
|
||||||
@@ -1808,7 +1819,8 @@ static int jvp_object_equal(jv o1, jv o2) {
|
|||||||
jv* slot2 = jvp_object_read(o2, slot->string);
|
jv* slot2 = jvp_object_read(o2, slot->string);
|
||||||
if (!slot2) return 0;
|
if (!slot2) return 0;
|
||||||
// FIXME: do less refcounting here
|
// FIXME: do less refcounting here
|
||||||
if (!jv_equal(jv_copy(slot->value), jv_copy(*slot2))) return 0;
|
int r = jvp_equal(jv_copy(slot->value), jv_copy(*slot2), depth);
|
||||||
|
if (r <= 0) return r;
|
||||||
len1++;
|
len1++;
|
||||||
}
|
}
|
||||||
return len1 == len2;
|
return len1 == len2;
|
||||||
@@ -2067,7 +2079,16 @@ int jv_get_refcnt(jv j) {
|
|||||||
* Higher-level operations
|
* Higher-level operations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int jv_equal(jv a, jv b) {
|
#ifndef MAX_EQUAL_DEPTH
|
||||||
|
#define MAX_EQUAL_DEPTH (10000)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int jvp_equal(jv a, jv b, int depth) {
|
||||||
|
if (depth > MAX_EQUAL_DEPTH) {
|
||||||
|
jv_free(a);
|
||||||
|
jv_free(b);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
int r;
|
int r;
|
||||||
if (jv_get_kind(a) != jv_get_kind(b)) {
|
if (jv_get_kind(a) != jv_get_kind(b)) {
|
||||||
r = 0;
|
r = 0;
|
||||||
@@ -2083,13 +2104,13 @@ int jv_equal(jv a, jv b) {
|
|||||||
r = jvp_number_equal(a, b);
|
r = jvp_number_equal(a, b);
|
||||||
break;
|
break;
|
||||||
case JV_KIND_ARRAY:
|
case JV_KIND_ARRAY:
|
||||||
r = jvp_array_equal(a, b);
|
r = jvp_array_equal(a, b, depth + 1);
|
||||||
break;
|
break;
|
||||||
case JV_KIND_STRING:
|
case JV_KIND_STRING:
|
||||||
r = jvp_string_equal(a, b);
|
r = jvp_string_equal(a, b);
|
||||||
break;
|
break;
|
||||||
case JV_KIND_OBJECT:
|
case JV_KIND_OBJECT:
|
||||||
r = jvp_object_equal(a, b);
|
r = jvp_object_equal(a, b, depth + 1);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
r = 1;
|
r = 1;
|
||||||
@@ -2101,6 +2122,11 @@ int jv_equal(jv a, jv b) {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns 1 if equal, 0 if not equal, or -1 if the comparison is too deep
|
||||||
|
int jv_equal(jv a, jv b) {
|
||||||
|
return jvp_equal(a, b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
int jv_identical(jv a, jv b) {
|
int jv_identical(jv a, jv b) {
|
||||||
int r;
|
int r;
|
||||||
if (a.kind_flags != b.kind_flags
|
if (a.kind_flags != b.kind_flags
|
||||||
|
|||||||
+89
-15
@@ -16,6 +16,24 @@ static double jv_number_get_value_and_consume(jv number) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef MAX_CMP_DEPTH
|
||||||
|
#define MAX_CMP_DEPTH (10000)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct sort_cmp_state {
|
||||||
|
int too_deep;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
static __declspec(thread) struct sort_cmp_state sort_cmp_state;
|
||||||
|
#else
|
||||||
|
#ifdef HAVE___THREAD
|
||||||
|
static __thread struct sort_cmp_state sort_cmp_state;
|
||||||
|
#else
|
||||||
|
static struct sort_cmp_state sort_cmp_state;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
static jv parse_slice(jv j, jv slice, int* pstart, int* pend) {
|
static jv parse_slice(jv j, jv slice, int* pstart, int* pend) {
|
||||||
// Array slices
|
// Array slices
|
||||||
jv start_jv = jv_object_get(jv_copy(slice), jv_string("start"));
|
jv start_jv = jv_object_get(jv_copy(slice), jv_string("start"));
|
||||||
@@ -463,7 +481,7 @@ static jv delpaths_sorted(jv object, jv paths, int start) {
|
|||||||
do
|
do
|
||||||
j++;
|
j++;
|
||||||
while (j < jv_array_length(jv_copy(paths)) &&
|
while (j < jv_array_length(jv_copy(paths)) &&
|
||||||
jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start)));
|
jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start)) == 1);
|
||||||
// if i <= entry < j, then entry starts with key
|
// if i <= entry < j, then entry starts with key
|
||||||
if (delkey) {
|
if (delkey) {
|
||||||
// deleting this entire key, we don't care about any more specific deletions
|
// deleting this entire key, we don't care about any more specific deletions
|
||||||
@@ -597,7 +615,13 @@ jv jv_keys(jv x) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int jv_cmp(jv a, jv b) {
|
static int jvp_cmp(jv a, jv b, int depth) {
|
||||||
|
if (depth > MAX_CMP_DEPTH) {
|
||||||
|
jv_free(a);
|
||||||
|
jv_free(b);
|
||||||
|
return INT_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
if (jv_get_kind(a) != jv_get_kind(b)) {
|
if (jv_get_kind(a) != jv_get_kind(b)) {
|
||||||
int r = (int)jv_get_kind(a) - (int)jv_get_kind(b);
|
int r = (int)jv_get_kind(a) - (int)jv_get_kind(b);
|
||||||
jv_free(a);
|
jv_free(a);
|
||||||
@@ -612,14 +636,13 @@ int jv_cmp(jv a, jv b) {
|
|||||||
case JV_KIND_FALSE:
|
case JV_KIND_FALSE:
|
||||||
case JV_KIND_TRUE:
|
case JV_KIND_TRUE:
|
||||||
// there's only one of each of these values
|
// there's only one of each of these values
|
||||||
r = 0;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case JV_KIND_NUMBER: {
|
case JV_KIND_NUMBER: {
|
||||||
if (jvp_number_is_nan(a)) {
|
if (jvp_number_is_nan(a)) {
|
||||||
r = jv_cmp(jv_null(), jv_copy(b));
|
r = jvp_cmp(jv_null(), jv_copy(b), depth);
|
||||||
} else if (jvp_number_is_nan(b)) {
|
} else if (jvp_number_is_nan(b)) {
|
||||||
r = jv_cmp(jv_copy(a), jv_null());
|
r = jvp_cmp(jv_copy(a), jv_null(), depth);
|
||||||
} else {
|
} else {
|
||||||
r = jvp_number_cmp(a, b);
|
r = jvp_number_cmp(a, b);
|
||||||
}
|
}
|
||||||
@@ -643,7 +666,9 @@ int jv_cmp(jv a, jv b) {
|
|||||||
}
|
}
|
||||||
jv xa = jv_array_get(jv_copy(a), i);
|
jv xa = jv_array_get(jv_copy(a), i);
|
||||||
jv xb = jv_array_get(jv_copy(b), i);
|
jv xb = jv_array_get(jv_copy(b), i);
|
||||||
r = jv_cmp(xa, xb);
|
r = jvp_cmp(xa, xb, depth + 1);
|
||||||
|
if (r == INT_MIN)
|
||||||
|
break;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -652,13 +677,14 @@ int jv_cmp(jv a, jv b) {
|
|||||||
case JV_KIND_OBJECT: {
|
case JV_KIND_OBJECT: {
|
||||||
jv keys_a = jv_keys(jv_copy(a));
|
jv keys_a = jv_keys(jv_copy(a));
|
||||||
jv keys_b = jv_keys(jv_copy(b));
|
jv keys_b = jv_keys(jv_copy(b));
|
||||||
r = jv_cmp(jv_copy(keys_a), keys_b);
|
r = jvp_cmp(jv_copy(keys_a), keys_b, depth + 1);
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
jv_array_foreach(keys_a, i, key) {
|
jv_array_foreach(keys_a, i, key) {
|
||||||
jv xa = jv_object_get(jv_copy(a), jv_copy(key));
|
jv xa = jv_object_get(jv_copy(a), jv_copy(key));
|
||||||
jv xb = jv_object_get(jv_copy(b), key);
|
jv xb = jv_object_get(jv_copy(b), key);
|
||||||
r = jv_cmp(xa, xb);
|
r = jvp_cmp(xa, xb, depth + 1);
|
||||||
if (r) break;
|
if (r != 0)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jv_free(keys_a);
|
jv_free(keys_a);
|
||||||
@@ -671,6 +697,11 @@ int jv_cmp(jv a, jv b) {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns <0, 0, >0 if a is less than, equal to, or greater than b, or
|
||||||
|
// INT_MIN if the comparison is too deep
|
||||||
|
int jv_cmp(jv a, jv b) {
|
||||||
|
return jvp_cmp(a, b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
struct sort_entry {
|
struct sort_entry {
|
||||||
jv object;
|
jv object;
|
||||||
@@ -678,19 +709,32 @@ struct sort_entry {
|
|||||||
int index;
|
int index;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void sort_entry_array_free(struct sort_entry* entries, int start, int n) {
|
||||||
|
for (int i = start; i < n; i++) {
|
||||||
|
jv_free(entries[i].key);
|
||||||
|
jv_free(entries[i].object);
|
||||||
|
}
|
||||||
|
jv_mem_free(entries);
|
||||||
|
}
|
||||||
|
|
||||||
static int sort_cmp(const void* pa, const void* pb) {
|
static int sort_cmp(const void* pa, const void* pb) {
|
||||||
const struct sort_entry* a = pa;
|
const struct sort_entry* a = pa;
|
||||||
const struct sort_entry* b = pb;
|
const struct sort_entry* b = pb;
|
||||||
int r = jv_cmp(jv_copy(a->key), jv_copy(b->key));
|
int r = jv_cmp(jv_copy(a->key), jv_copy(b->key));
|
||||||
|
if (r == INT_MIN) {
|
||||||
|
sort_cmp_state.too_deep = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
// comparing by index if r == 0 makes the sort stable
|
// comparing by index if r == 0 makes the sort stable
|
||||||
return r ? r : (a->index - b->index);
|
return r ? r : (a->index - b->index);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sort_entry* sort_items(jv objects, jv keys) {
|
static struct sort_entry* sort_items(jv objects, jv keys, int *too_deep) {
|
||||||
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
|
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
|
||||||
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
||||||
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
||||||
int n = jv_array_length(jv_copy(objects));
|
int n = jv_array_length(jv_copy(objects));
|
||||||
|
*too_deep = 0;
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
jv_free(objects);
|
jv_free(objects);
|
||||||
jv_free(keys);
|
jv_free(keys);
|
||||||
@@ -704,7 +748,13 @@ static struct sort_entry* sort_items(jv objects, jv keys) {
|
|||||||
}
|
}
|
||||||
jv_free(objects);
|
jv_free(objects);
|
||||||
jv_free(keys);
|
jv_free(keys);
|
||||||
|
sort_cmp_state.too_deep = 0;
|
||||||
qsort(entries, n, sizeof(struct sort_entry), sort_cmp);
|
qsort(entries, n, sizeof(struct sort_entry), sort_cmp);
|
||||||
|
if (sort_cmp_state.too_deep) {
|
||||||
|
sort_entry_array_free(entries, 0, n);
|
||||||
|
*too_deep = 1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,7 +763,10 @@ jv jv_sort(jv objects, jv keys) {
|
|||||||
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
||||||
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
||||||
int n = jv_array_length(jv_copy(objects));
|
int n = jv_array_length(jv_copy(objects));
|
||||||
struct sort_entry* entries = sort_items(objects, keys);
|
int too_deep = 0;
|
||||||
|
struct sort_entry* entries = sort_items(objects, keys, &too_deep);
|
||||||
|
if (too_deep)
|
||||||
|
return jv_invalid_with_msg(jv_string("Comparison too deep"));
|
||||||
jv ret = jv_array();
|
jv ret = jv_array();
|
||||||
for (int i=0; i<n; i++) {
|
for (int i=0; i<n; i++) {
|
||||||
jv_free(entries[i].key);
|
jv_free(entries[i].key);
|
||||||
@@ -728,13 +781,24 @@ jv jv_group(jv objects, jv keys) {
|
|||||||
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
||||||
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
||||||
int n = jv_array_length(jv_copy(objects));
|
int n = jv_array_length(jv_copy(objects));
|
||||||
struct sort_entry* entries = sort_items(objects, keys);
|
int too_deep = 0;
|
||||||
|
struct sort_entry* entries = sort_items(objects, keys, &too_deep);
|
||||||
|
if (too_deep)
|
||||||
|
return jv_invalid_with_msg(jv_string("Comparison too deep"));
|
||||||
jv ret = jv_array();
|
jv ret = jv_array();
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
jv curr_key = entries[0].key;
|
jv curr_key = entries[0].key;
|
||||||
jv group = jv_array_append(jv_array(), entries[0].object);
|
jv group = jv_array_append(jv_array(), entries[0].object);
|
||||||
for (int i = 1; i < n; i++) {
|
for (int i = 1; i < n; i++) {
|
||||||
if (jv_equal(jv_copy(curr_key), jv_copy(entries[i].key))) {
|
int equal = jv_equal(jv_copy(curr_key), jv_copy(entries[i].key));
|
||||||
|
if (equal < 0) {
|
||||||
|
jv_free(curr_key);
|
||||||
|
jv_free(group);
|
||||||
|
sort_entry_array_free(entries, i, n);
|
||||||
|
jv_free(ret);
|
||||||
|
return jv_invalid_with_msg(jv_string("Equality check too deep"));
|
||||||
|
}
|
||||||
|
if (equal) {
|
||||||
jv_free(entries[i].key);
|
jv_free(entries[i].key);
|
||||||
} else {
|
} else {
|
||||||
jv_free(curr_key);
|
jv_free(curr_key);
|
||||||
@@ -756,11 +820,21 @@ jv jv_unique(jv objects, jv keys) {
|
|||||||
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
||||||
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
||||||
int n = jv_array_length(jv_copy(objects));
|
int n = jv_array_length(jv_copy(objects));
|
||||||
struct sort_entry* entries = sort_items(objects, keys);
|
int too_deep = 0;
|
||||||
|
struct sort_entry* entries = sort_items(objects, keys, &too_deep);
|
||||||
|
if (too_deep)
|
||||||
|
return jv_invalid_with_msg(jv_string("Comparison too deep"));
|
||||||
jv ret = jv_array();
|
jv ret = jv_array();
|
||||||
jv curr_key = jv_invalid();
|
jv curr_key = jv_invalid();
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
if (jv_equal(jv_copy(curr_key), jv_copy(entries[i].key))) {
|
int equal = jv_equal(jv_copy(curr_key), jv_copy(entries[i].key));
|
||||||
|
if (equal < 0) {
|
||||||
|
jv_free(curr_key);
|
||||||
|
sort_entry_array_free(entries, i, n);
|
||||||
|
jv_free(ret);
|
||||||
|
return jv_invalid_with_msg(jv_string("Equality check too deep"));
|
||||||
|
}
|
||||||
|
if (equal) {
|
||||||
jv_free(entries[i].key);
|
jv_free(entries[i].key);
|
||||||
jv_free(entries[i].object);
|
jv_free(entries[i].object);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2611,3 +2611,25 @@ null
|
|||||||
try (reduce range(10001) as $_ ({}; {a: .}) as $x | $x * $x) catch .
|
try (reduce range(10001) as $_ ({}; {a: .}) as $x | $x * $x) catch .
|
||||||
null
|
null
|
||||||
"Object merge too deep"
|
"Object merge too deep"
|
||||||
|
|
||||||
|
# regression test for deep structural equality recursion
|
||||||
|
try ((reduce range(10001) as $_ ([]; [.])) as $x | (reduce range(10001) as $_ ([]; [.])) as $y | $x == $y) catch .
|
||||||
|
null
|
||||||
|
"Equality check too deep"
|
||||||
|
|
||||||
|
# regression tests for deep ordering comparisons
|
||||||
|
try ((reduce range(10001) as $_ ([]; [.])) as $x | [$x, $x] | sort) catch .
|
||||||
|
null
|
||||||
|
"Comparison too deep"
|
||||||
|
|
||||||
|
try ((reduce range(10001) as $_ ([]; [.])) as $x | [$x, $x] | unique) catch .
|
||||||
|
null
|
||||||
|
"Comparison too deep"
|
||||||
|
|
||||||
|
try ((reduce range(10001) as $_ ({}; {a: .})) as $x | [$x, $x] | sort) catch .
|
||||||
|
null
|
||||||
|
"Comparison too deep"
|
||||||
|
|
||||||
|
try ((reduce range(10001) as $_ ({}; {a: .})) as $x | [$x, $x] | unique) catch .
|
||||||
|
null
|
||||||
|
"Comparison too deep"
|
||||||
Reference in new issue
Block a user