From 6afbaad187b0ab4638242241c9dfb5cab0212fe0 Mon Sep 17 00:00:00 2001 From: Stefan Gula Date: Sun, 28 Sep 2025 17:18:28 +0200 Subject: [PATCH] xpath UPDATE to support usage of union types as deref targets This patch introduces ability to use union types, which internally uses leafrefs to be used as deref target nodes. This fullfils the last statement of RFC 7950 section 10.3.1. This behavior is also allowed within leafref paths which uses deref function in it. This patch also fixes segfault in case of using cyclic deref paths --- src/path.c | 132 +++++++++++++++++++++++++---------- src/xpath.c | 112 ++++++++++++++++++++--------- tests/utests/types/leafref.c | 44 +++++++++++- 3 files changed, 217 insertions(+), 71 deletions(-) diff --git a/src/path.c b/src/path.c index 74d4fe1f9..00cdf1f76 100644 --- a/src/path.c +++ b/src/path.c @@ -1020,6 +1020,95 @@ ly_path_append(const struct ly_ctx *ctx, const struct ly_path *src, struct ly_pa return ret; } +/** + * @brief Compile deref XPath function into ly_path structure. + * + * @param[in] ctx libyang context. + * @param[in] cur_node Current (original context) node. + * @param[in] target_node The deref target schema node. + * @param[in] cur_type The currently evaluated type of deref target node. + * @param[in] top_ext Extension instance containing the definition of the data being created. It is used to find + * the top-level node inside the extension instance instead of a module. Note that this is the case not only if + * the @p ctx_node is NULL, but also if the relative path starting in @p ctx_node reaches the document root + * via double dots. + * @param[in] expr Parsed path. + * @param[in] oper Oper option (@ref path_oper_options). + * @param[in] target Target option (@ref path_target_options). + * @param[in] format Format of the path. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] log Whether to generate log message or not + * @param[in,out] tok_idx Index in @p exp, is adjusted. + * @param[out] path Compiled path. + * @return LY_ERR value. + */ +static LY_ERR +ly_path_compile_deref_type(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const struct lysc_node *target_node, + const struct lysc_type *cur_type, const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint16_t oper, uint16_t target, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool log, uint32_t *tok_idx, struct ly_path **path) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr expr2; + struct ly_path *path2 = NULL; + const struct lysc_type_union *union_type; + const struct lysc_type_leafref *lref; + uint32_t cur_tok_idx = *tok_idx; + LY_ARRAY_COUNT_TYPE u; + + if (cur_type->basetype == LY_TYPE_UNION) { + union_type = (const struct lysc_type_union *)cur_type; + ret = LY_EVALID; + LY_ARRAY_FOR(union_type->types, u) { + *tok_idx = cur_tok_idx; + if (ly_path_compile_deref_type(ctx, cur_node, target_node, union_type->types[u], top_ext, expr, oper, target, format, prefix_data, 0, tok_idx, path) == LY_SUCCESS) { + ret = LY_SUCCESS; + } + } + if (log && ret) { + LOGVAL_PATH(ctx, cur_node, target_node, LYVE_XPATH, "Deref function target node \"%s\" is union type with no leafrefs.", target_node->name); + } + goto cleanup; + } else if (cur_type->basetype != LY_TYPE_LEAFREF) { + if (log) { + LOGVAL_PATH(ctx, cur_node, target_node, LYVE_XPATH, "Deref function target node \"%s\" is not leafref.", target_node->name); + } + ret = LY_EVALID; + goto cleanup; + } + lref = (const struct lysc_type_leafref *)cur_type; + + /* compile dereferenced leafref expression and append it to the path */ + LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, target_node, top_ext, lref->path, oper, target, format, prefix_data, + &path2), cleanup); + target_node = path2[LY_ARRAY_COUNT(path2) - 1].node; + LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); + ly_path_free(path2); + path2 = NULL; + + /* properly parsed path must always continue with ')' and '/' */ + assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2)); + (*tok_idx)++; + assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_OPER_PATH)); + (*tok_idx)++; + + /* prepare expr representing rest of the path after deref */ + expr2.tokens = &expr->tokens[*tok_idx]; + expr2.tok_pos = &expr->tok_pos[*tok_idx]; + expr2.tok_len = &expr->tok_len[*tok_idx]; + expr2.repeat = &expr->repeat[*tok_idx]; + expr2.used = expr->used - *tok_idx; + expr2.size = expr->size - *tok_idx; + expr2.expr = expr->expr; + + /* compile rest of the path and append it to the path */ + LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, target_node, top_ext, &expr2, oper, target, format, prefix_data, &path2), + cleanup); + LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); + +cleanup: + ly_path_free(path2); + return ret; +} + /** * @brief Compile deref XPath function into ly_path structure. * @@ -1049,7 +1138,6 @@ ly_path_compile_deref(const struct ly_ctx *ctx, const struct lysc_node *cur_node struct ly_path *path2 = NULL; const struct lysc_node *node2; const struct lysc_node_leaf *deref_leaf_node; - const struct lysc_type_leafref *lref; uint32_t begin_token; *path = NULL; @@ -1083,50 +1171,22 @@ ly_path_compile_deref(const struct ly_ctx *ctx, const struct lysc_node *cur_node LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, ctx_node, top_ext, &expr2, oper, target, format, prefix_data, &path2), cleanup); node2 = path2[LY_ARRAY_COUNT(path2) - 1].node; - if ((node2->nodetype != LYS_LEAF) && (node2->nodetype != LYS_LEAFLIST)) { - LOGVAL_PATH(ctx, cur_node, node2, LYVE_XPATH, "Deref function target node \"%s\" is not leaf nor leaflist.", + if (node2 == ctx_node) { + LOGVAL_PATH(ctx, cur_node, node2, LYVE_XPATH, "Deref function target node \"%s\" is node itself.", node2->name); ret = LY_EVALID; goto cleanup; } - deref_leaf_node = (const struct lysc_node_leaf *)node2; - if (deref_leaf_node->type->basetype != LY_TYPE_LEAFREF) { - LOGVAL_PATH(ctx, cur_node, node2, LYVE_XPATH, "Deref function target node \"%s\" is not leafref.", node2->name); + if ((node2->nodetype != LYS_LEAF) && (node2->nodetype != LYS_LEAFLIST)) { + LOGVAL_PATH(ctx, cur_node, node2, LYVE_XPATH, "Deref function target node \"%s\" is not leaf nor leaflist.", + node2->name); ret = LY_EVALID; goto cleanup; } - lref = (const struct lysc_type_leafref *)deref_leaf_node->type; - LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); - ly_path_free(path2); - path2 = NULL; - /* compile dereferenced leafref expression and append it to the path */ - LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, lref->path, oper, target, format, prefix_data, - &path2), cleanup); - node2 = path2[LY_ARRAY_COUNT(path2) - 1].node; - LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); - ly_path_free(path2); - path2 = NULL; - - /* properly parsed path must always continue with ')' and '/' */ - assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2)); - (*tok_idx)++; - assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_OPER_PATH)); - (*tok_idx)++; - - /* prepare expr representing rest of the path after deref */ - expr2.tokens = &expr->tokens[*tok_idx]; - expr2.tok_pos = &expr->tok_pos[*tok_idx]; - expr2.tok_len = &expr->tok_len[*tok_idx]; - expr2.repeat = &expr->repeat[*tok_idx]; - expr2.used = expr->used - *tok_idx; - expr2.size = expr->size - *tok_idx; - expr2.expr = expr->expr; - - /* compile rest of the path and append it to the path */ - LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, &expr2, oper, target, format, prefix_data, &path2), - cleanup); LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); + deref_leaf_node = (const struct lysc_node_leaf *)node2; + ret = ly_path_compile_deref_type(ctx, cur_node, node2, deref_leaf_node->type, top_ext, expr, oper, target, format, prefix_data, 1, tok_idx, path); cleanup: ly_path_free(path2); diff --git a/src/xpath.c b/src/xpath.c index b2c69cd5e..5c3a26937 100644 --- a/src/xpath.c +++ b/src/xpath.c @@ -3980,6 +3980,83 @@ xpath_current(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, return LY_SUCCESS; } +/** + * @brief Executes deref function on specific type and value. It performs evaluation in recursive manner, + * which supports usage of union types as deref targets. Returns LYXP_SET_NODE_SET with either + * leafref or instance-identifier target node(s). + * + * @param[in] leaf The target deref data node + * @param[in] sleaf The target deref schema node + * @param[in] value The currently evaluated value of target deref node depending on current type + * @param[in] cur_type The currently evaluated type of target deref node + * @param[in] log Whether to generate log message or not + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_deref_type(struct lyd_node_term *leaf, struct lysc_node_leaf *sleaf, struct lyd_value *value, struct lysc_type *cur_type, ly_bool log, struct lyxp_set *set) +{ + LY_ERR r; + LY_ERR ret = LY_SUCCESS; + char *errmsg = NULL; + struct lyd_node *node; + struct ly_set *targets = NULL; + uint32_t i; + const struct lysc_type_union *union_type; + LY_ARRAY_COUNT_TYPE u; + + if (sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { + if (cur_type->basetype == LY_TYPE_LEAFREF) { + /* find leafref target */ + r = lyplg_type_resolve_leafref((struct lysc_type_leafref *)cur_type, &leaf->node, value, set->tree, + set->ext, &targets, &errmsg); + if (r) { + if (log) { + LOGERR(set->ctx, LY_EINVAL, "%s", errmsg); + } + free(errmsg); + ret = LY_EINVAL; + goto cleanup; + } + + /* insert nodes into set */ + for (i = 0; i < targets->count; ++i) { + set_insert_node(set, targets->dnodes[i], 0, LYXP_NODE_ELEM, 0); + } + } else if (cur_type->basetype == LY_TYPE_INST) { + if (ly_path_eval(value->target, set->tree, NULL, set->ext, &node)) { + if (log) { + LOGERR(set->ctx, LY_EINVAL, "Invalid instance-identifier \"%s\" value - required instance not found.", + lyd_get_value(&leaf->node)); + } + ret = LY_EINVAL; + goto cleanup; + } + + /* insert it */ + set_insert_node(set, node, 0, LYXP_NODE_ELEM, 0); + } else if (cur_type->basetype == LY_TYPE_UNION) { + union_type = (const struct lysc_type_union *)cur_type; + ret = LY_EINVAL; + LY_ARRAY_FOR(union_type->types, u) { + if (!xpath_deref_type(leaf, sleaf, &value->subvalue->value, union_type->types[u], 0, set)) { + ret = LY_SUCCESS; + goto cleanup; + } + } + if (log) { + LOGERR(set->ctx, LY_EINVAL, "Invalid leafref or instance-identifier \"%s\" value - required instance not found.", + lyd_get_value(&leaf->node)); + } + } + } + +cleanup: + ly_set_free(targets, NULL); + return ret; +} + /** * @brief Execute the YANG 1.1 deref(node-set) function. Returns LYXP_SET_NODE_SET with either * leafref or instance-identifier target node(s). @@ -3998,13 +4075,9 @@ xpath_deref(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set struct lysc_type_leafref *lref; const struct lysc_node *target; struct ly_path *p; - struct lyd_node *node; - char *errmsg = NULL; uint8_t oper; LY_ERR r; LY_ERR ret = LY_SUCCESS; - struct ly_set *targets = NULL; - uint32_t i; if (options & LYXP_SCNODE_ALL) { if (args[0]->type != LYXP_SET_SCNODE_SET) { @@ -4050,39 +4123,10 @@ xpath_deref(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set if (args[0]->used) { leaf = (struct lyd_node_term *)args[0]->val.nodes[0].node; sleaf = (struct lysc_node_leaf *)leaf->schema; - if (sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { - if (sleaf->type->basetype == LY_TYPE_LEAFREF) { - /* find leafref target */ - r = lyplg_type_resolve_leafref((struct lysc_type_leafref *)sleaf->type, &leaf->node, &leaf->value, set->tree, - set->ext, &targets, &errmsg); - if (r) { - LOGERR(set->ctx, LY_EINVAL, "%s", errmsg); - free(errmsg); - ret = LY_EINVAL; - goto cleanup; - } - - /* insert nodes into set */ - for (i = 0; i < targets->count; ++i) { - set_insert_node(set, targets->dnodes[i], 0, LYXP_NODE_ELEM, 0); - } - } else { - assert(sleaf->type->basetype == LY_TYPE_INST); - if (ly_path_eval(leaf->value.target, set->tree, NULL, set->ext, &node)) { - LOGERR(set->ctx, LY_EINVAL, "Invalid instance-identifier \"%s\" value - required instance not found.", - lyd_get_value(&leaf->node)); - ret = LY_EINVAL; - goto cleanup; - } - - /* insert it */ - set_insert_node(set, node, 0, LYXP_NODE_ELEM, 0); - } - } + ret = xpath_deref_type(leaf, sleaf, &leaf->value, sleaf->type, 1, set); } cleanup: - ly_set_free(targets, NULL); return ret; } diff --git a/tests/utests/types/leafref.c b/tests/utests/types/leafref.c index af782873d..ff332bbef 100644 --- a/tests/utests/types/leafref.c +++ b/tests/utests/types/leafref.c @@ -278,10 +278,45 @@ test_data_xpath_json(void **state) lyd_free_all(tree); } +static void +test_data_xpath_deref_union(void **state) +{ + const char *schema, *data; + struct lyd_node *tree; + + ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED); + + /* json xpath test */ + schema = MODULE_CREATE_YANG("xp_test", + "list l1 {key k; leaf k {type string;} leaf v {type string;}}" + "list l2 {key k; leaf k {type uint8;} leaf v {type string;}}" + "leaf ref1 {type union {type leafref {path \"../l1/k\";} type leafref {path \"../l2/k\";}}}" + "leaf ref2 {type leafref {path \"deref(../ref1)/../v\";}}" + "leaf ref3 {type union {type leafref {path \"../l1/k\";} type leafref {path \"../l2/k\";}}}" + "leaf ref4 {type leafref {path \"deref(../ref3)/../v\";}}" + "leaf ref5 {type union {type leafref {path \"../l1/k\";} type string;}}" + "leaf ref6 {type leafref {path \"deref(../ref5)/../v\";}}"); + + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); + + data = "{" + " \"xp_test:l1\": [{\"k\": \"key1\", \"v\": \"value1\"}, {\"k\": \"key2\", \"v\": \"value2\"}]," + " \"xp_test:l2\": [{\"k\": 1, \"v\": \"value3\" }, {\"k\": 2, \"v\": \"value4\"}]," + " \"xp_test:ref1\": \"key2\"," + " \"xp_test:ref2\": \"value2\"," + " \"xp_test:ref3\": 2," + " \"xp_test:ref4\": \"value4\"," + " \"xp_test:ref5\": \"key2\"," + " \"xp_test:ref6\": \"value2\"" + "}"; + CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + lyd_free_all(tree); +} + static void test_xpath_invalid_schema(void **state) { - const char *schema1, *schema2; + const char *schema1, *schema2, *schema3; ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED); schema1 = MODULE_CREATE_YANG("xp_test", @@ -308,6 +343,12 @@ test_xpath_invalid_schema(void **state) UTEST_INVALID_MODULE(schema2, LYS_IN_YANG, NULL, LY_EVALID) CHECK_LOG_CTX("Deref function target node \"r1\" is not leafref.", "/xp_test:r2", 0); + + schema3 = MODULE_CREATE_YANG("xp_test", + "leaf v1 {type string;}" + "leaf r1 {type leafref {path \"deref(../r1)/../v1\";}}"); + UTEST_INVALID_MODULE(schema3, LYS_IN_YANG, NULL, LY_EVALID) + CHECK_LOG_CTX("Deref function target node \"r1\" is node itself.", "/xp_test:r1", 0); } int @@ -319,6 +360,7 @@ main(void) UTEST(test_plugin_lyb), UTEST(test_plugin_sort), UTEST(test_data_xpath_json), + UTEST(test_data_xpath_deref_union), UTEST(test_xpath_invalid_schema) };