diff --git a/execute.c b/execute.c index 7295d72d..9f3b5499 100644 --- a/execute.c +++ b/execute.c @@ -34,6 +34,7 @@ struct jq_state { stack_ptr fork_top; jv path; + jv value_at_path; int subexp_nest; int debug_trace_enabled; int initial_execution; @@ -191,6 +192,7 @@ struct forkpoint { stack_ptr saved_data_stack; stack_ptr saved_curr_frame; int path_len, subexp_nest; + jv value_at_path; uint16_t* return_address; }; @@ -210,20 +212,33 @@ void stack_save(jq_state *jq, uint16_t* retaddr, struct stack_pos sp){ fork->saved_curr_frame = jq->curr_frame; fork->path_len = jv_get_kind(jq->path) == JV_KIND_ARRAY ? jv_array_length(jv_copy(jq->path)) : 0; + fork->value_at_path = jv_copy(jq->value_at_path); fork->subexp_nest = jq->subexp_nest; fork->return_address = retaddr; jq->stk_top = sp.saved_data_stack; jq->curr_frame = sp.saved_curr_frame; } -void path_append(jq_state* jq, jv component) { +static int path_intact(jq_state *jq, jv curr) { + if (jq->subexp_nest == 0 && jv_get_kind(jq->path) == JV_KIND_ARRAY) { + return jv_identical(curr, jv_copy(jq->value_at_path)); + } else { + jv_free(curr); + return 1; + } +} + +static void path_append(jq_state* jq, jv component, jv value_at_path) { if (jq->subexp_nest == 0 && jv_get_kind(jq->path) == JV_KIND_ARRAY) { int n1 = jv_array_length(jv_copy(jq->path)); jq->path = jv_array_append(jq->path, component); int n2 = jv_array_length(jv_copy(jq->path)); assert(n2 == n1 + 1); + jv_free(jq->value_at_path); + jq->value_at_path = value_at_path; } else { jv_free(component); + jv_free(value_at_path); } } @@ -253,6 +268,8 @@ uint16_t* stack_restore(jq_state *jq){ } else { assert(path_len == 0); } + jv_free(jq->value_at_path); + jq->value_at_path = fork->value_at_path; jq->subexp_nest = fork->subexp_nest; jq->fork_top = stack_pop_block(&jq->stk, jq->fork_top, sizeof(struct forkpoint)); return retaddr; @@ -271,6 +288,8 @@ static void jq_reset(jq_state *jq) { if (jv_get_kind(jq->path) != JV_KIND_INVALID) jv_free(jq->path); jq->path = jv_null(); + jv_free(jq->value_at_path); + jq->value_at_path = jv_null(); jq->subexp_nest = 0; } @@ -536,17 +555,29 @@ jv jq_next(jq_state *jq) { stack_save(jq, pc - 1, stack_get_pos(jq)); stack_push(jq, jv_number(jq->subexp_nest)); - stack_push(jq, v); + stack_push(jq, jq->value_at_path); + stack_push(jq, jv_copy(v)); jq->path = jv_array(); + jq->value_at_path = v; // next INDEX operation must index into v jq->subexp_nest = 0; break; } case PATH_END: { jv v = stack_pop(jq); + // detect invalid path expression like path(.a | reverse) + if (!path_intact(jq, jv_copy(v))) { + char errbuf[30]; + jv msg = jv_string_fmt( + "Invalid path expression with result %s", + jv_dump_string_trunc(v, errbuf, sizeof(errbuf))); + set_error(jq, jv_invalid_with_msg(msg)); + goto do_backtrack; + } jv_free(v); // discard value, only keep path + jv old_value_at_path = stack_pop(jq); int old_subexp_nest = (int)jv_number_value(stack_pop(jq)); jv path = jq->path; @@ -558,6 +589,8 @@ jv jq_next(jq_state *jq) { stack_push(jq, path); jq->subexp_nest = old_subexp_nest; + jv_free(jq->value_at_path); + jq->value_at_path = old_value_at_path; break; } @@ -572,11 +605,23 @@ jv jq_next(jq_state *jq) { case INDEX_OPT: { jv t = stack_pop(jq); jv k = stack_pop(jq); - path_append(jq, jv_copy(k)); - jv v = jv_get(t, k); + // detect invalid path expression like path(reverse | .a) + if (!path_intact(jq, jv_copy(t))) { + char keybuf[15]; + char objbuf[30]; + jv msg = jv_string_fmt( + "Invalid path expression near attempt to access element %s of %s", + jv_dump_string_trunc(k, keybuf, sizeof(keybuf)), + jv_dump_string_trunc(t, objbuf, sizeof(objbuf))); + set_error(jq, jv_invalid_with_msg(msg)); + goto do_backtrack; + } + jv v = jv_get(t, jv_copy(k)); if (jv_is_valid(v)) { + path_append(jq, k, jv_copy(v)); stack_push(jq, v); } else { + jv_free(k); if (opcode == INDEX) set_error(jq, v); else @@ -605,9 +650,21 @@ jv jq_next(jq_state *jq) { } case EACH: - case EACH_OPT: + case EACH_OPT: { + jv container = stack_pop(jq); + // detect invalid path expression like path(reverse | .[]) + if (!path_intact(jq, jv_copy(container))) { + char errbuf[30]; + jv msg = jv_string_fmt( + "Invalid path expression near attempt to iterate through %s", + jv_dump_string_trunc(container, errbuf, sizeof(errbuf))); + set_error(jq, jv_invalid_with_msg(msg)); + goto do_backtrack; + } + stack_push(jq, container); stack_push(jq, jv_number(-1)); // fallthrough + } case ON_BACKTRACK(EACH): case ON_BACKTRACK(EACH_OPT): { int idx = jv_number_value(stack_pop(jq)); @@ -653,14 +710,14 @@ jv jq_next(jq_state *jq) { } else if (is_last) { // we don't need to make a backtrack point jv_free(container); - path_append(jq, key); + path_append(jq, key, jv_copy(value)); stack_push(jq, value); } else { struct stack_pos spos = stack_get_pos(jq); stack_push(jq, container); stack_push(jq, jv_number(idx)); stack_save(jq, pc - 1, spos); - path_append(jq, key); + path_append(jq, key, jv_copy(value)); stack_push(jq, value); } break; @@ -880,6 +937,7 @@ jq_state *jq_init(void) { jq->attrs = jv_object(); jq->path = jv_null(); + jq->value_at_path = jv_null(); return jq; } diff --git a/jv.c b/jv.c index 40d1a869..6a446ae3 100644 --- a/jv.c +++ b/jv.c @@ -137,10 +137,7 @@ static void jvp_invalid_free(jv x) { */ jv jv_number(double x) { - jv j; - j.kind_flags = JV_KIND_NUMBER; - j.size = 0; - j.u.number = x; + jv j = {JV_KIND_NUMBER, 0, 0, 0, {.number = x}}; return j; } @@ -1291,6 +1288,32 @@ int jv_equal(jv a, jv b) { return r; } +int jv_identical(jv a, jv b) { + int r; + if (a.kind_flags != b.kind_flags + || a.offset != b.offset + || a.size != b.size) { + r = 0; + } else { + switch (jv_get_kind(a)) { + case JV_KIND_ARRAY: + case JV_KIND_STRING: + case JV_KIND_OBJECT: + r = a.u.ptr == b.u.ptr; + break; + case JV_KIND_NUMBER: + r = a.u.number == b.u.number; + break; + default: + r = 1; + break; + } + } + jv_free(a); + jv_free(b); + return r; +} + int jv_contains(jv a, jv b) { int r = 1; if (jv_get_kind(a) != jv_get_kind(b)) { diff --git a/jv.h b/jv.h index 05cb61f8..5de3b639 100644 --- a/jv.h +++ b/jv.h @@ -46,6 +46,7 @@ void jv_free(jv); int jv_get_refcnt(jv); int jv_equal(jv, jv); +int jv_identical(jv, jv); int jv_contains(jv, jv); jv jv_invalid(void); diff --git a/tests/jq.test b/tests/jq.test index bf10abd7..941d825a 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -695,6 +695,26 @@ path(.) 42 [] +try path(.a | map(select(.b == 0))) catch . +{"a":[{"b":0}]} +"Invalid path expression with result [{\"b\":0}]" + +try path(.a | map(select(.b == 0)) | .[0]) catch . +{"a":[{"b":0}]} +"Invalid path expression near attempt to access element 0 of [{\"b\":0}]" + +try path(.a | map(select(.b == 0)) | .c) catch . +{"a":[{"b":0}]} +"Invalid path expression near attempt to access element \"c\" of [{\"b\":0}]" + +try path(.a | map(select(.b == 0)) | .[]) catch . +{"a":[{"b":0}]} +"Invalid path expression near attempt to iterate through [{\"b\":0}]" + +path(.a[path(.b)[0]]) +{"a":{"b":0}} +["a","b"] + [paths] [1,[[],{"a":2}]] [[0],[1],[1,0],[1,1],[1,1,"a"]] @@ -783,6 +803,22 @@ def inc(x): x |= .+1; inc(.[].a) {"foo":[11], "bar":42} {"foo":[11,null,{"bar":1}], "bar":42} +try ((map(select(.a == 1))[].b) = 10) catch . +[{"a":0},{"a":1}] +"Invalid path expression near attempt to iterate through [{\"a\":1}]" + +try ((map(select(.a == 1))[].a) |= .+1) catch . +[{"a":0},{"a":1}] +"Invalid path expression near attempt to iterate through [{\"a\":1}]" + +def x: .[1,2]; x=10 +[0,1,2] +[0,10,10] + +try (def x: reverse; x=10) catch . +[0,1,2] +"Invalid path expression with result [2,1,0]" + # # Conditionals #