path UPDTAE deref func support in leafref (#2030)
diff --git a/src/context.h b/src/context.h
index 10bbef4..a6367f5 100644
--- a/src/context.h
+++ b/src/context.h
@@ -197,6 +197,8 @@
#define LY_CTX_ENABLE_IMP_FEATURES 0x0100 /**< By default, all features of newly implemented imported modules of
a module that is being loaded are disabled. With this flag they all become
enabled. */
+#define LY_CTX_LEAFREF_EXTENDED 0x0200 /**< By default, path attribute of leafref accepts only path as defined in RFC 7950.
+ By using this option, the path attribute will also allow using XPath functions as deref() */
/** @} contextoptions */
diff --git a/src/hash_table.c b/src/hash_table.c
index 0523d8e..246fb34 100644
--- a/src/hash_table.c
+++ b/src/hash_table.c
@@ -537,7 +537,7 @@
int32_t i;
ly_bool first_matched = 0;
LY_ERR r, ret = LY_SUCCESS;
- lyht_value_equal_cb old_val_equal;
+ lyht_value_equal_cb old_val_equal = NULL;
LY_CHECK_ERR_RET(lyht_find_first(ht, hash, &rec), LOGARG(NULL, hash), LY_ENOTFOUND); /* hash not found */
diff --git a/src/path.c b/src/path.c
index cfa934f..4bfb76d 100644
--- a/src/path.c
+++ b/src/path.c
@@ -264,6 +264,60 @@
return LY_EVALID;
}
+/**
+ * @brief Parse deref XPath function and perform all additional checks.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] ctx_node Optional context node, used for logging.
+ * @param[in] exp Parsed path.
+ * @param[in,out] tok_idx Index in @p exp, is adjusted.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_parse_deref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const struct lyxp_expr *exp,
+ uint32_t *tok_idx)
+{
+ size_t arg_len;
+ uint32_t begin_token, end_token;
+ struct lyxp_expr *arg_expr = NULL;
+
+ /* mandatory FunctionName */
+ LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_FUNCNAME), LY_EVALID);
+ if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "deref", 5)) {
+ LOGVAL(ctx, LYVE_XPATH, "Unexpected XPath function \"%.*s\" in path, expected \"deref(...)\"",
+ exp->tok_len[*tok_idx], exp->tok_pos[*tok_idx]);
+ return LY_EVALID;
+ }
+
+ /* mandatory '(' */
+ LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR1), LY_EVALID);
+ begin_token = *tok_idx;
+
+ /* count tokens till ')' */
+ while (lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_PAR2) && *tok_idx < exp->used) {
+ /* emebedded functions are not allowed */
+ if (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_FUNCNAME)) {
+ LOGVAL(ctx, LYVE_XPATH, "Embedded function XPath function inside deref function within the path"
+ "is not allowed");
+ return LY_EVALID;
+ }
+
+ (*tok_idx)++;
+ }
+
+ /* mandatory ')' */
+ LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR2), LY_EVALID);
+ end_token = *tok_idx - 1;
+
+ /* parse the path of deref argument */
+ arg_len = exp->tok_pos[end_token] - exp->tok_pos[begin_token];
+ LY_CHECK_RET(ly_path_parse(ctx, ctx_node, &exp->expr[exp->tok_pos[begin_token]], arg_len, 1,
+ LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &arg_expr), LY_EVALID);
+ lyxp_expr_free(ctx, arg_expr);
+
+ return LY_SUCCESS;
+}
+
LY_ERR
ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *str_path, size_t path_len,
ly_bool lref, uint16_t begin, uint16_t prefix, uint16_t pred, struct lyxp_expr **expr)
@@ -294,12 +348,23 @@
if (lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_OPER_PATH)) {
/* relative path check specific to leafref */
if (lref) {
+ /* optional function 'deref..' */
+ if ((ly_ctx_get_options(ctx) & LY_CTX_LEAFREF_EXTENDED) &&
+ !lyxp_check_token(NULL, exp, tok_idx, LYXP_TOKEN_FUNCNAME)) {
+ LY_CHECK_ERR_GOTO(ly_path_parse_deref(ctx, ctx_node, exp, &tok_idx), ret = LY_EVALID, error);
+
+ /* '/' */
+ LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID,
+ error);
+ }
+
/* mandatory '..' */
LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_DDOT), ret = LY_EVALID, error);
do {
/* '/' */
- LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID, error);
+ LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID,
+ error);
/* optional '..' */
} while (!lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_DDOT));
@@ -866,6 +931,189 @@
}
/**
+ * @brief Duplicate ly_path_predicate structure.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] pred The array of path predicates.
+ * @param[out] dup Duplicated predicates.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_dup_predicates(const struct ly_ctx *ctx, const struct ly_path_predicate *pred, struct ly_path_predicate **dup)
+{
+ LY_ARRAY_COUNT_TYPE u;
+
+ if (!pred) {
+ return LY_SUCCESS;
+ }
+
+ LY_ARRAY_CREATE_RET(ctx, *dup, LY_ARRAY_COUNT(pred), LY_EMEM);
+ LY_ARRAY_FOR(pred, u) {
+ LY_ARRAY_INCREMENT(*dup);
+ (*dup)[u].type = pred->type;
+
+ switch (pred->type) {
+ case LY_PATH_PREDTYPE_POSITION:
+ /* position-predicate */
+ (*dup)[u].position = pred->position;
+ break;
+ case LY_PATH_PREDTYPE_LIST:
+ case LY_PATH_PREDTYPE_LEAFLIST:
+ /* key-predicate or leaf-list-predicate */
+ (*dup)[u].key = pred->key;
+ pred->value.realtype->plugin->duplicate(ctx, &pred->value, &(*dup)[u].value);
+ LY_ATOMIC_INC_BARRIER(((struct lysc_type *)pred->value.realtype)->refcount);
+ break;
+ case LY_PATH_PREDTYPE_LIST_VAR:
+ /* key-predicate with a variable */
+ (*dup)[u].key = pred->key;
+ (*dup)[u].variable = strdup(pred->variable);
+ break;
+ }
+ }
+
+ return LY_SUCCESS;
+}
+
+/**
+ * @brief Appends path elements from source to destination array
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] src The source path
+ * @param[in,out] dst The destination path
+ * @return LY_ERR value.
+ */
+static LY_ERR
+ly_path_append(const struct ly_ctx *ctx, const struct ly_path *src, struct ly_path **dst)
+{
+ LY_ERR ret = LY_SUCCESS;
+ LY_ARRAY_COUNT_TYPE u;
+ struct ly_path *p;
+
+ if (!src) {
+ return LY_SUCCESS;
+ }
+
+ LY_ARRAY_CREATE_RET(ctx, *dst, LY_ARRAY_COUNT(src), LY_EMEM);
+ LY_ARRAY_FOR(src, u) {
+ LY_ARRAY_NEW_GOTO(ctx, *dst, p, ret, error);
+ p->node = src[u].node;
+ p->ext = src[u].ext;
+ LY_CHECK_GOTO(ret = ly_path_dup_predicates(ctx, src[u].predicates, &p->predicates), error);
+ }
+
+ return LY_SUCCESS;
+
+error:
+ ly_path_free(ctx, *dst);
+ return (ret == LY_ENOTFOUND) ? LY_EVALID : ret;
+}
+
+/**
+ * @brief Compile deref XPath function into ly_path structure.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] ctx_node Optional context node, mandatory of @p lref.
+ * @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,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(const struct ly_ctx *ctx, const struct lysc_node *ctx_node,
+ 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, 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_node *node2;
+ const struct lysc_node_leaf *deref_leaf_node;
+ const struct lysc_type_leafref *lref;
+ uint32_t begin_token;
+
+ /* properly parsed path must always starts with 'deref' and '(' */
+ assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_FUNCNAME));
+ assert(!strncmp(&expr->expr[expr->tok_pos[*tok_idx]], "deref", 5));
+ (*tok_idx)++;
+ assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR1));
+ (*tok_idx)++;
+ begin_token = *tok_idx;
+
+ /* emebedded functions were already identified count tokens till ')' */
+ while (lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2) && (*tok_idx < expr->used)) {
+ (*tok_idx)++;
+ }
+
+ /* properly parsed path must have ')' within the tokens */
+ assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2));
+
+ /* prepare expr representing just deref arg */
+ expr2.tokens = &expr->tokens[begin_token];
+ expr2.tok_pos = &expr->tok_pos[begin_token];
+ expr2.tok_len = &expr->tok_len[begin_token];
+ expr2.repeat = &expr->repeat[begin_token];
+ expr2.used = *tok_idx - begin_token;
+ expr2.size = expr->size - begin_token;
+ expr2.expr = expr->expr;
+
+ /* compile just deref arg, append it to the path and find dereferenced lref for next operations */
+ LY_CHECK_ERR_GOTO(ly_path_compile_leafref(ctx, ctx_node, top_ext, &expr2, oper, target, format, prefix_data,
+ &path2), ret = LY_EVALID, cleanup);
+ node2 = path2[LY_ARRAY_COUNT(path2) - 1].node;
+ deref_leaf_node = (const struct lysc_node_leaf *)node2;
+ lref = (const struct lysc_type_leafref *)deref_leaf_node->type;
+ LY_CHECK_ERR_GOTO(ly_path_append(ctx, path2, path), ret = LY_EVALID, cleanup);
+ ly_path_free(ctx, path2);
+ path2 = NULL;
+
+ /* compile dereferenced leafref expression and append it to the path */
+ LY_CHECK_ERR_GOTO(ly_path_compile_leafref(ctx, node2, top_ext, lref->path, oper, target, format, prefix_data,
+ &path2), ret = LY_EVALID, cleanup);
+ node2 = path2[LY_ARRAY_COUNT(path2) - 1].node;
+ LY_CHECK_ERR_GOTO(ly_path_append(ctx, path2, path), ret = LY_EVALID, cleanup);
+ ly_path_free(ctx, 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_ERR_GOTO(ly_path_compile_leafref(ctx, node2, top_ext, &expr2, oper, target, format, prefix_data, &path2),
+ ret = LY_EVALID, cleanup);
+ LY_CHECK_ERR_GOTO(ly_path_append(ctx, path2, path), ret = LY_EVALID, cleanup);
+
+cleanup:
+ ly_path_free(ctx, path2);
+ if (ret) {
+ ly_path_free(ctx, *path);
+ *path = NULL;
+ }
+ LOG_LOCBACK(1, 0, 0, 0);
+ return (ret == LY_ENOTFOUND) ? LY_EVALID : ret;
+}
+
+/**
* @brief Compile path into ly_path structure. Any predicates of a leafref are only checked, not compiled.
*
* @param[in] ctx libyang context.
@@ -922,7 +1170,13 @@
getnext_opts = 0;
}
- if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) {
+ if (lref && (ly_ctx_get_options(ctx) & LY_CTX_LEAFREF_EXTENDED) &&
+ (expr->tokens[tok_idx] == LYXP_TOKEN_FUNCNAME)) {
+ /* deref function */
+ ret = ly_path_compile_deref(ctx, ctx_node, top_ext, expr, oper, target, format, prefix_data, &tok_idx, path);
+ ctx_node = (*path)[LY_ARRAY_COUNT(*path) - 1].node;
+ goto cleanup;
+ } else if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) {
/* absolute path */
ctx_node = NULL;
@@ -1158,7 +1412,8 @@
LY_ERR
ly_path_dup(const struct ly_ctx *ctx, const struct ly_path *path, struct ly_path **dup)
{
- LY_ARRAY_COUNT_TYPE u, v;
+ LY_ERR ret = LY_SUCCESS;
+ LY_ARRAY_COUNT_TYPE u;
if (!path) {
return LY_SUCCESS;
@@ -1168,34 +1423,8 @@
LY_ARRAY_FOR(path, u) {
LY_ARRAY_INCREMENT(*dup);
(*dup)[u].node = path[u].node;
- if (path[u].predicates) {
- LY_ARRAY_CREATE_RET(ctx, (*dup)[u].predicates, LY_ARRAY_COUNT(path[u].predicates), LY_EMEM);
- LY_ARRAY_FOR(path[u].predicates, v) {
- struct ly_path_predicate *pred = &path[u].predicates[v];
-
- LY_ARRAY_INCREMENT((*dup)[u].predicates);
- (*dup)[u].predicates[v].type = pred->type;
-
- switch (pred->type) {
- case LY_PATH_PREDTYPE_POSITION:
- /* position-predicate */
- (*dup)[u].predicates[v].position = pred->position;
- break;
- case LY_PATH_PREDTYPE_LIST:
- case LY_PATH_PREDTYPE_LEAFLIST:
- /* key-predicate or leaf-list-predicate */
- (*dup)[u].predicates[v].key = pred->key;
- pred->value.realtype->plugin->duplicate(ctx, &pred->value, &(*dup)[u].predicates[v].value);
- LY_ATOMIC_INC_BARRIER(((struct lysc_type *)pred->value.realtype)->refcount);
- break;
- case LY_PATH_PREDTYPE_LIST_VAR:
- /* key-predicate with a variable */
- (*dup)[u].predicates[v].key = pred->key;
- (*dup)[u].predicates[v].variable = strdup(pred->variable);
- break;
- }
- }
- }
+ (*dup)[u].ext = path[u].ext;
+ LY_CHECK_RET(ret = ly_path_dup_predicates(ctx, path[u].predicates, &(*dup)[u].predicates), ret);
}
return LY_SUCCESS;
diff --git a/src/tree_data.c b/src/tree_data.c
index 778f174..41e616c 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -99,7 +99,7 @@
lyd_parse(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, struct lyd_node **first_p,
struct ly_in *in, LYD_FORMAT format, uint32_t parse_opts, uint32_t val_opts, struct lyd_node **op)
{
- LY_ERR r, rc = LY_SUCCESS;
+ LY_ERR r = LY_SUCCESS, rc = LY_SUCCESS;
struct lyd_ctx *lydctx = NULL;
struct ly_set parsed = {0};
struct lyd_node *first;
diff --git a/src/xpath.c b/src/xpath.c
index b676dd9..0f1eaae 100644
--- a/src/xpath.c
+++ b/src/xpath.c
@@ -6662,7 +6662,7 @@
{
ly_bool temp_ctx = 0;
uint32_t getnext_opts, orig_used, i, mod_idx, idx;
- const struct lys_module *mod;
+ const struct lys_module *mod = NULL;
const struct lysc_node *iter;
enum lyxp_node_type iter_type;
diff --git a/tests/utests/types/leafref.c b/tests/utests/types/leafref.c
index c8d0cb6..7005d08 100644
--- a/tests/utests/types/leafref.c
+++ b/tests/utests/types/leafref.c
@@ -209,6 +209,38 @@
TEST_SUCCESS_LYB("lyb", "lst", "key_str", "lref", "key_str");
}
+static void
+test_data_xpath_json(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 t1;"
+ "leaf t1 {type uint8;}"
+ "list l2 {key t2;"
+ "leaf t2 {type uint8;}"
+ "leaf-list l3 {type uint8;}"
+ "}}"
+ "leaf r1 {type leafref {path \"../l1/t1\";}}"
+ "leaf r2 {type leafref {path \"deref(../r1)/../l2/t2\";}}"
+ "leaf r3 {type leafref {path \"deref(../r2)/../l3\";}}");
+
+ UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL);
+
+ data = "{"
+ " \"xp_test:l1\":[{\"t1\": 1,\"l2\":[{\"t2\": 2,\"l3\":[3]}]}],"
+ " \"xp_test:r1\": 1,"
+ " \"xp_test:r2\": 2,"
+ " \"xp_test:r3\": 3"
+ "}";
+ CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+ lyd_free_all(tree);
+}
+
int
main(void)
{
@@ -216,6 +248,7 @@
UTEST(test_data_xml),
UTEST(test_data_json),
UTEST(test_plugin_lyb),
+ UTEST(test_data_xpath_json),
};
return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tools/lint/common.h b/tools/lint/common.h
index c3b8f95..0a7c54f 100644
--- a/tools/lint/common.h
+++ b/tools/lint/common.h
@@ -257,6 +257,6 @@
* @param[in] schema_path Path to the wanted node.
* @return Pointer to the schema node specified by the path on success, NULL otherwise.
*/
-const struct lysc_node * find_schema_path(const struct ly_ctx *ctx, const char *schema_path);
+const struct lysc_node *find_schema_path(const struct ly_ctx *ctx, const char *schema_path);
#endif /* COMMON_H_ */
diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c
index 6e00339..a0849f8 100644
--- a/tools/lint/main_ni.c
+++ b/tools/lint/main_ni.c
@@ -287,6 +287,9 @@
" create an exact YANG schema context. If specified, the '-F'\n"
" parameter (enabled features) is ignored.\n\n");
+ printf(" -X, --extended-leafref\n"
+ " Allow usage of deref() XPath function within leafref\n\n");
+
#ifndef NDEBUG
printf(" -G GROUPS, --debug=GROUPS\n"
" Enable printing of specific debugging message group\n"
@@ -558,6 +561,7 @@
{"merge", no_argument, NULL, 'm'},
{"yang-library", no_argument, NULL, 'y'},
{"yang-library-file", required_argument, NULL, 'Y'},
+ {"extended-leafref", no_argument, NULL, 'X'},
#ifndef NDEBUG
{"debug", required_argument, NULL, 'G'},
#endif
@@ -572,9 +576,9 @@
opterr = 0;
#ifndef NDEBUG
- while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:G:", options, &opt_index)) != -1)
+ while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:Xx:G:", options, &opt_index)) != -1)
#else
- while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:", options, &opt_index)) != -1)
+ while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:Xx:", options, &opt_index)) != -1)
#endif
{
switch (opt) {
@@ -814,6 +818,10 @@
c->yang_lib_file = optarg;
break;
+ case 'X': /* --extended-leafref */
+ c->ctx_options |= LY_CTX_LEAFREF_EXTENDED;
+ break;
+
#ifndef NDEBUG
case 'G': { /* --debug */
uint32_t dbg_groups = 0;