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;