mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-09 09:11:00 +08:00
fix(cypher): reject unconsumed query tokens
cbm_parse previously returned success when parse_return_item stopped before postfix list indexing or an arbitrary trailing token. A query containing labels(n)[0] could therefore ignore its alias, aggregate, ordering, and limit and execute as an unbounded projection. Require TOK_EOF after parse_post_where in src/cypher/cypher.c and transfer the parent cursor after a recursively validated UNION tail. Route both list-index rejection paths through one diagnostic that gives supported labels(n) and n.label/count(*) rewrites. Add parser regressions in tests/test_cypher.c. Verified 155/155 Cypher tests under ASan/UBSan, all three UNION tests, source-safety, an optimized CLI rejection, and the supported five-row node-label aggregation. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
+23
-2
@@ -1548,6 +1548,13 @@ static int parse_string_func_item(parser_t *p, cbm_return_item_t *item) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_unsupported_list_index_error(parser_t *p) {
|
||||
snprintf(p->error, sizeof(p->error),
|
||||
"unsupported expression: list indexing/slicing '[...]' is not supported; return "
|
||||
"labels(n) AS labels directly, or group scalar node labels with RETURN n.label AS "
|
||||
"label, count(*) AS node_count ORDER BY node_count DESC LIMIT 5");
|
||||
}
|
||||
|
||||
static int parse_return_item(parser_t *p, cbm_return_item_t *item) {
|
||||
memset(item, 0, sizeof(*item));
|
||||
int rc = 0;
|
||||
@@ -1583,8 +1590,7 @@ static int parse_return_item(parser_t *p, cbm_return_item_t *item) {
|
||||
"trim, ltrim, rtrim, reverse, labels, type, id, keys, properties)",
|
||||
item->variable ? item->variable : "?");
|
||||
} else {
|
||||
snprintf(p->error, sizeof(p->error),
|
||||
"unsupported expression: list indexing/slicing '[...]' is not supported");
|
||||
set_unsupported_list_index_error(p);
|
||||
}
|
||||
safe_str_free(&item->variable);
|
||||
safe_str_free(&item->property);
|
||||
@@ -1880,6 +1886,8 @@ static int parse_post_where(parser_t *p, cbm_query_t *q, // NOLINT(misc-no-recur
|
||||
q->union_next = sub.query;
|
||||
sub.query = NULL;
|
||||
cbm_parse_free(&sub);
|
||||
/* The recursive parser owns and validates the complete UNION tail. */
|
||||
p->pos = p->count - SKIP_ONE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1943,6 +1951,19 @@ int cbm_parse(const cbm_token_t *tokens, int token_count, // NOLINT(misc-no-recu
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (!check(&p, TOK_EOF)) {
|
||||
if (check(&p, TOK_LBRACKET)) {
|
||||
set_unsupported_list_index_error(&p);
|
||||
out->error = heap_strdup(p.error);
|
||||
} else {
|
||||
snprintf(p.error, sizeof(p.error), "unexpected trailing token '%s' at pos %d",
|
||||
peek(&p)->text, peek(&p)->pos);
|
||||
out->error = heap_strdup(p.error);
|
||||
}
|
||||
cbm_query_free(q);
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
|
||||
out->query = q;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2769,6 +2769,35 @@ TEST(cypher_issue240_labels_function) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cypher_rejects_list_index_after_function_result) {
|
||||
cbm_query_t *q = NULL;
|
||||
char *err = NULL;
|
||||
int rc = cbm_cypher_parse("MATCH (n) RETURN labels(n)[0] AS label, count(*) AS count "
|
||||
"ORDER BY count DESC LIMIT 5",
|
||||
&q, &err);
|
||||
ASSERT(rc != 0);
|
||||
ASSERT_NULL(q);
|
||||
ASSERT_NOT_NULL(err);
|
||||
ASSERT_NOT_NULL(strstr(err, "list indexing"));
|
||||
ASSERT_NOT_NULL(strstr(err, "labels(n) AS labels"));
|
||||
ASSERT_NOT_NULL(strstr(err, "n.label AS label"));
|
||||
ASSERT_NOT_NULL(strstr(err, "count(*) AS node_count"));
|
||||
free(err);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cypher_rejects_unconsumed_trailing_tokens) {
|
||||
cbm_query_t *q = NULL;
|
||||
char *err = NULL;
|
||||
int rc = cbm_cypher_parse("MATCH (n) RETURN n.name trailing", &q, &err);
|
||||
ASSERT(rc != 0);
|
||||
ASSERT_NULL(q);
|
||||
ASSERT_NOT_NULL(err);
|
||||
ASSERT_NOT_NULL(strstr(err, "unexpected trailing token"));
|
||||
free(err);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #237: DISTINCT applied before ORDER BY + LIMIT */
|
||||
TEST(cypher_issue237_distinct_order_limit) {
|
||||
cbm_store_t *s = setup_cypher_store();
|
||||
@@ -2969,6 +2998,8 @@ SUITE(cypher) {
|
||||
/* Execution */
|
||||
RUN_TEST(cypher_exec_match_all_functions);
|
||||
RUN_TEST(cypher_issue240_labels_function);
|
||||
RUN_TEST(cypher_rejects_list_index_after_function_result);
|
||||
RUN_TEST(cypher_rejects_unconsumed_trailing_tokens);
|
||||
RUN_TEST(cypher_issue237_distinct_order_limit);
|
||||
RUN_TEST(cypher_issue873_distinct_order_limit_dedupes_before_limit);
|
||||
RUN_TEST(cypher_issue873_distinct_limit_dedupes_before_limit);
|
||||
|
||||
Reference in New Issue
Block a user