context UPDATE support for storing leafref references (#2155)

* Adding leafref references and API to lyd_node_term

* Adjusted to require context flag

* Extended the utest for tree_data

* Fixed typo in docs

* Refactored based on PR discussion

* Refactored to use hash table for leafref nodes
diff --git a/src/common.h b/src/common.h
index 0fedeae..0157d26 100644
--- a/src/common.h
+++ b/src/common.h
@@ -373,6 +373,7 @@
     void *ext_clb_data;               /**< optional private data for ::ly_ctx.ext_clb */
     struct ly_ht *err_ht;             /**< hash table of thread-specific list of errors related to the context */
     pthread_mutex_t lyb_hash_lock;    /**< lock for storing LYB schema hashes in schema nodes */
+    struct ly_ht *leafref_links_ht;   /**< hash table of leafref links between term data nodes */
 };
 
 /**
diff --git a/src/context.c b/src/context.c
index 7a14203..0c41019 100644
--- a/src/context.c
+++ b/src/context.c
@@ -239,6 +239,30 @@
     return !memcmp(&err1->tid, &err2->tid, sizeof err1->tid);
 }
 
+/**
+ * @brief Hash table value-equal callback for comparing leafref links hash table record.
+ */
+static ly_bool
+ly_ctx_ht_leafref_links_equal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data))
+{
+    struct lyd_leafref_links_rec *rec1 = val1_p, *rec2 = val2_p;
+
+    return rec1->node == rec2->node;
+}
+
+/**
+ * @brief Callback for freeing leafref links recorcd internal resources.
+ *
+ * @param[in] val_p Pointer to leafref links record
+ */
+static void
+ly_ctx_ht_leafref_links_rec_free(void *val_p)
+{
+    struct lyd_leafref_links_rec *rec = val_p;
+
+    lyd_free_leafref_links_rec(rec);
+}
+
 LIBYANG_API_DEF LY_ERR
 ly_ctx_new(const char *search_dir, uint16_t options, struct ly_ctx **new_ctx)
 {
@@ -262,6 +286,11 @@
     /* plugins */
     LY_CHECK_ERR_GOTO(lyplg_init(), LOGINT(NULL); rc = LY_EINT, cleanup);
 
+    if (options & LY_CTX_LEAFREF_LINKING) {
+        ctx->leafref_links_ht = lyht_new(1, sizeof(struct lyd_leafref_links_rec), ly_ctx_ht_leafref_links_equal_cb, NULL, 1);
+        LY_CHECK_ERR_GOTO(!ctx->leafref_links_ht, rc = LY_EMEM, cleanup);
+    }
+
     /* initialize thread-specific error hash table */
     ctx->err_ht = lyht_new(1, sizeof(struct ly_ctx_err_rec), ly_ctx_ht_err_equal_cb, NULL, 1);
     LY_CHECK_ERR_GOTO(!ctx->err_ht, rc = LY_EMEM, cleanup);
@@ -588,6 +617,11 @@
     LY_CHECK_ERR_RET((option & LY_CTX_NO_YANGLIBRARY) && !(ctx->flags & LY_CTX_NO_YANGLIBRARY),
             LOGARG(ctx, option), LY_EINVAL);
 
+    if (!(ctx->flags & LY_CTX_LEAFREF_LINKING) && (option & LY_CTX_LEAFREF_LINKING)) {
+        ctx->leafref_links_ht = lyht_new(1, sizeof(struct lyd_leafref_links_rec), ly_ctx_ht_leafref_links_equal_cb, NULL, 1);
+        LY_CHECK_ERR_RET(!ctx->leafref_links_ht, LOGARG(ctx, option), LY_EMEM);
+    }
+
     if (!(ctx->flags & LY_CTX_SET_PRIV_PARSED) && (option & LY_CTX_SET_PRIV_PARSED)) {
         ctx->flags |= LY_CTX_SET_PRIV_PARSED;
         /* recompile the whole context to set the priv pointers */
@@ -628,6 +662,11 @@
     LY_CHECK_ARG_RET(ctx, ctx, LY_EINVAL);
     LY_CHECK_ERR_RET(option & LY_CTX_NO_YANGLIBRARY, LOGARG(ctx, option), LY_EINVAL);
 
+    if ((ctx->flags & LY_CTX_LEAFREF_LINKING) && (option & LY_CTX_LEAFREF_LINKING)) {
+        lyht_free(ctx->leafref_links_ht, ly_ctx_ht_leafref_links_rec_free);
+        ctx->leafref_links_ht = NULL;
+    }
+
     if ((ctx->flags & LY_CTX_SET_PRIV_PARSED) && (option & LY_CTX_SET_PRIV_PARSED)) {
         struct lys_module *mod;
         uint32_t index;
@@ -1318,6 +1357,11 @@
     /* leftover unres */
     lys_unres_glob_erase(&ctx->unres);
 
+    /* clean the leafref links hash table */
+    if (ctx->leafref_links_ht) {
+        lyht_free(ctx->leafref_links_ht, ly_ctx_ht_leafref_links_rec_free);
+    }
+
     /* clean the error hash table */
     lyht_free(ctx->err_ht, ly_ctx_ht_err_rec_free);
 
diff --git a/src/context.h b/src/context.h
index a6367f5..318c614 100644
--- a/src/context.h
+++ b/src/context.h
@@ -199,6 +199,10 @@
                                         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() */
+#define LY_CTX_LEAFREF_LINKING 0x0400 /**< Link valid leafref nodes with its target during validation if leafref node is not using
+                                        'require-instance false;'. It also enables usage of
+                                        [lyd_leafref_get_links](@ref lyd_leafref_get_links) and
+                                        [lyd_leafref_link_node_tree](@ref lyd_leafref_link_node_tree) APIs. */
 
 /** @} contextoptions */
 
diff --git a/src/plugins_types/leafref.c b/src/plugins_types/leafref.c
index 8ab3fc5..8e60f98 100644
--- a/src/plugins_types/leafref.c
+++ b/src/plugins_types/leafref.c
@@ -25,6 +25,7 @@
 #include "common.h"
 #include "compat.h"
 #include "plugins_internal.h" /* LY_TYPE_*_STR */
+#include "tree_data_internal.h" /* lyd_link_leafref_node */
 
 /**
  * @page howtoDataLYB LYB Binary Format
@@ -63,12 +64,13 @@
 }
 
 LIBYANG_API_DEF LY_ERR
-lyplg_type_validate_leafref(const struct ly_ctx *UNUSED(ctx), const struct lysc_type *type, const struct lyd_node *ctx_node,
+lyplg_type_validate_leafref(const struct ly_ctx *ctx, const struct lysc_type *type, const struct lyd_node *ctx_node,
         const struct lyd_node *tree, struct lyd_value *storage, struct ly_err_item **err)
 {
     LY_ERR ret;
     struct lysc_type_leafref *type_lr = (struct lysc_type_leafref *)type;
     char *errmsg = NULL, *path;
+    struct lyd_node *target = NULL;
 
     *err = NULL;
 
@@ -78,13 +80,17 @@
     }
 
     /* check leafref target existence */
-    if (lyplg_type_resolve_leafref(type_lr, ctx_node, storage, tree, NULL, &errmsg)) {
+    if (lyplg_type_resolve_leafref(type_lr, ctx_node, storage, tree, &target, &errmsg)) {
         path = lyd_path(ctx_node, LYD_PATH_STD, NULL, 0);
         ret = ly_err_new(err, LY_EVALID, LYVE_DATA, path, strdup("instance-required"), "%s", errmsg);
         free(errmsg);
         return ret;
     }
 
+    if (ly_ctx_get_options(ctx) & LY_CTX_LEAFREF_LINKING) {
+        return lyd_link_leafref_node((struct lyd_node_term *)target, (struct lyd_node_term *)ctx_node);
+    }
+
     return LY_SUCCESS;
 }
 
diff --git a/src/tree_data.c b/src/tree_data.c
index 2b007ef..9ccf994 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -911,6 +911,11 @@
     /* update hashes while still linked into the tree */
     lyd_unlink_hash(node);
 
+    /* unlink leafref nodes */
+    if (node->schema && (node->schema->nodetype & LYD_NODE_TERM)) {
+        lyd_free_leafref_nodes((struct lyd_node_term *)node);
+    }
+
     /* unlink from siblings */
     if (node->prev->next) {
         node->prev->next = node->next;
@@ -3268,3 +3273,148 @@
 
     return NULL;
 }
+
+LY_ERR
+lyd_get_or_create_leafref_links_record(const struct lyd_node_term *node, struct lyd_leafref_links_rec **record, ly_bool create)
+{
+    struct ly_ht *ht;
+    uint32_t hash;
+    struct lyd_leafref_links_rec rec;
+
+    assert(node);
+    assert(record);
+
+    if (!(ly_ctx_get_options(LYD_CTX(node)) & LY_CTX_LEAFREF_LINKING)) {
+        *record = NULL;
+        return LY_EDENIED;
+    }
+
+    rec.node = node;
+    rec.leafref_nodes = NULL;
+    rec.target_node = NULL;
+
+    ht = LYD_CTX(node)->leafref_links_ht;
+    hash = lyht_hash((const char *)&node, sizeof & node);
+    if (lyht_find(ht, &rec, hash, (void **)record) == LY_ENOTFOUND) {
+        if (create) {
+            LY_CHECK_RET(lyht_insert_no_check(ht, &rec, hash, (void **)record));
+        } else {
+            *record = NULL;
+            return LY_ENOTFOUND;
+        }
+    }
+
+    return LY_SUCCESS;
+}
+
+LIBYANG_API_DEF LY_ERR
+lyd_leafref_get_links(const struct lyd_node_term *node, const struct lyd_leafref_links_rec **record)
+{
+    LY_CHECK_ARG_RET(NULL, node, record, LY_EINVAL);
+
+    return lyd_get_or_create_leafref_links_record(node, (struct lyd_leafref_links_rec **)record, 0);
+}
+
+LY_ERR
+lyd_link_leafref_node(const struct lyd_node_term *node, const struct lyd_node_term *leafref_node)
+{
+    LY_ARRAY_COUNT_TYPE u;
+    const struct lyd_node_term **item = NULL;
+    struct lyd_leafref_links_rec *rec;
+
+    assert(node);
+    assert(leafref_node);
+
+    if (!(ly_ctx_get_options(LYD_CTX(node)) & LY_CTX_LEAFREF_LINKING)) {
+        return LY_EDENIED;
+    }
+
+    LY_CHECK_RET(lyd_get_or_create_leafref_links_record(node, &rec, 1));
+    LY_ARRAY_FOR(rec->leafref_nodes, u) {
+        if (rec->leafref_nodes[u] == leafref_node) {
+            return LY_SUCCESS;
+        }
+    }
+
+    LY_ARRAY_NEW_RET(LYD_CTX(node), rec->leafref_nodes, item, LY_EMEM);
+    *item = leafref_node;
+    LY_CHECK_RET(lyd_get_or_create_leafref_links_record(leafref_node, &rec, 1));
+    rec->target_node = node;
+    return LY_SUCCESS;
+}
+
+LIBYANG_API_DEF LY_ERR
+lyd_leafref_link_node_tree(const struct lyd_node *tree)
+{
+    const struct lyd_node *sibling, *elem;
+    struct lyd_node *target;
+    char *errmsg;
+    struct lyd_node_term *leafref_node;
+    struct lysc_node_leaf *leaf_schema;
+    struct lysc_type_leafref *lref;
+    LY_ERR ret;
+
+    LY_CHECK_ARG_RET(NULL, tree, LY_EINVAL);
+
+    if (!(ly_ctx_get_options(LYD_CTX(tree)) & LY_CTX_LEAFREF_LINKING)) {
+        return LY_EDENIED;
+    }
+
+    LY_LIST_FOR(tree, sibling) {
+        LYD_TREE_DFS_BEGIN(sibling, elem) {
+            if (elem->schema->nodetype & LYD_NODE_TERM) {
+                leafref_node = (struct lyd_node_term *)elem;
+                leaf_schema = (struct lysc_node_leaf *)elem->schema;
+                if (leaf_schema->type->basetype == LY_TYPE_LEAFREF) {
+                    lref = (struct lysc_type_leafref *)leaf_schema->type;
+                    if (lyplg_type_resolve_leafref(lref, elem, &leafref_node->value, tree, &target, &errmsg)) {
+                        free(errmsg);
+                    } else if (target->schema->nodetype & LYD_NODE_TERM) {
+                        ret = lyd_link_leafref_node((struct lyd_node_term *)target, leafref_node);
+                        if (ret != LY_SUCCESS) {
+                            return ret;
+                        }
+                    }
+                }
+            }
+            LYD_TREE_DFS_END(sibling, elem);
+        }
+    }
+    return LY_SUCCESS;
+}
+
+LY_ERR
+lyd_unlink_leafref_node(const struct lyd_node_term *node, const struct lyd_node_term *leafref_node)
+{
+    LY_ERR ret;
+    struct lyd_leafref_links_rec *rec;
+
+    assert(node);
+    assert(leafref_node);
+
+    if (!(ly_ctx_get_options(LYD_CTX(node)) & LY_CTX_LEAFREF_LINKING)) {
+        return LY_EDENIED;
+    }
+
+    ret = lyd_get_or_create_leafref_links_record(node, &rec, 0);
+    if (ret == LY_SUCCESS) {
+        LY_ARRAY_REMOVE_VALUE(rec->leafref_nodes, leafref_node);
+        if ((LY_ARRAY_COUNT(rec->leafref_nodes) == 0) && (rec->target_node == NULL)) {
+            lyd_free_leafref_nodes(node);
+        }
+    } else if (ret != LY_ENOTFOUND) {
+        return ret;
+    }
+
+    ret = lyd_get_or_create_leafref_links_record(leafref_node, &rec, 0);
+    if (ret == LY_SUCCESS) {
+        rec->target_node = NULL;
+        if ((LY_ARRAY_COUNT(rec->leafref_nodes) == 0) && (rec->target_node == NULL)) {
+            lyd_free_leafref_nodes(leafref_node);
+        }
+    } else if (ret != LY_ENOTFOUND) {
+        return ret;
+    }
+
+    return LY_SUCCESS;
+}
diff --git a/src/tree_data.h b/src/tree_data.h
index 4d87fba..0cce979 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -998,6 +998,22 @@
 };
 
 /**
+ * @brief Structure of leafref links record.
+ */
+struct lyd_leafref_links_rec {
+    const struct lyd_node_term *node;           /** pointer to the data node itself */
+    const struct lyd_node_term **leafref_nodes; /** list of the leafref pointing to this data node [sized array](@ref sizedarrays)),
+                                                    By default it is empty. It is filled automatically by validation function of
+                                                    leafref nodes, which are valid and are not using 'require-instance false;'.
+                                                    It can also be populated based on manual request using
+                                                    [link api](@ref lyd_leafref_link_node_tree). Freeing of the resources is
+                                                    automatic. */
+    const struct lyd_node_term *target_node;    /** pointer to leafref target data node, by default is NULL. The logic
+                                                    is the same as for [leafref_nodes](@ref leafref_nodes) and is filled only
+                                                    for leafrefs */
+};
+
+/**
  * @brief Get the generic parent pointer of a data node.
  *
  * @param[in] node Node whose parent pointer to get.
@@ -2702,6 +2718,29 @@
  */
 LIBYANG_API_DECL LY_ERR ly_time_ts2str(const struct timespec *ts, char **str);
 
+/**
+ * @brief Gets the leafref links record for given node
+ *
+ * This API requires usage of LY_CTX_LEAFREF_LINKING context flag.
+ *
+ * @param[in] node The term data node.
+ * @param[out] record The leafref links record
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+LIBYANG_API_DECL LY_ERR lyd_leafref_get_links(const struct lyd_node_term *node, const struct lyd_leafref_links_rec **record);
+
+/**
+ * @brief Traverse through data tree including root node siblings and adds leafrefs links to the given nodes
+ *
+ * This API requires usage of LY_CTX_LEAFREF_LINKING context flag.
+ *
+ * @param[in] tree The data tree root node.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+LIBYANG_API_DECL LY_ERR lyd_leafref_link_node_tree(const struct lyd_node *tree);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/tree_data_free.c b/src/tree_data_free.c
index 0281ae5..100cf49 100644
--- a/src/tree_data_free.c
+++ b/src/tree_data_free.c
@@ -138,6 +138,53 @@
     lyd_free_attr(ctx, attr, 1);
 }
 
+void
+lyd_free_leafref_links_rec(struct lyd_leafref_links_rec *rec)
+{
+    LY_ARRAY_COUNT_TYPE u;
+    struct lyd_leafref_links_rec *leafref_rec;
+
+    assert(rec);
+
+    /* remove stored leafref nodes */
+    LY_ARRAY_FOR(rec->leafref_nodes, u) {
+        if (lyd_get_or_create_leafref_links_record(rec->leafref_nodes[u], &leafref_rec, 0) == LY_SUCCESS) {
+            leafref_rec->target_node = NULL;
+            if ((LY_ARRAY_COUNT(leafref_rec->leafref_nodes) == 0) && (leafref_rec->target_node == NULL)) {
+                lyd_free_leafref_nodes(rec->leafref_nodes[u]);
+            }
+        }
+    }
+    LY_ARRAY_FREE(rec->leafref_nodes);
+    rec->leafref_nodes = NULL;
+    /* remove stored target node */
+    if (rec->target_node) {
+        lyd_unlink_leafref_node(rec->target_node, rec->node);
+    }
+}
+
+void
+lyd_free_leafref_nodes(const struct lyd_node_term *node)
+{
+    struct ly_ht *ht;
+    uint32_t hash;
+    struct lyd_leafref_links_rec *rec;
+
+    assert(node);
+
+    if (lyd_get_or_create_leafref_links_record(node, &rec, 0) != LY_SUCCESS) {
+        return;
+    }
+
+    /* free entry content */
+    lyd_free_leafref_links_rec(rec);
+
+    /* free entry itself from hash table */
+    ht = LYD_CTX(node)->leafref_links_ht;
+    hash = lyht_hash((const char *)&node, sizeof & node);
+    lyht_remove(ht, rec, hash);
+}
+
 /**
  * @brief Free Data (sub)tree.
  * @param[in] node Data node to be freed.
@@ -177,7 +224,10 @@
         /* only frees the value this way */
         lyd_any_copy_value(node, NULL, 0);
     } else if (node->schema->nodetype & LYD_NODE_TERM) {
-        ((struct lysc_node_leaf *)node->schema)->type->plugin->free(LYD_CTX(node), &((struct lyd_node_term *)node)->value);
+        struct lyd_node_term *node_term = (struct lyd_node_term *)node;
+
+        ((struct lysc_node_leaf *)node->schema)->type->plugin->free(LYD_CTX(node), &node_term->value);
+        lyd_free_leafref_nodes(node_term);
     }
 
     if (!node->schema) {
diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h
index cfb93f1..d639591 100644
--- a/src/tree_data_internal.h
+++ b/src/tree_data_internal.h
@@ -591,4 +591,55 @@
  */
 LY_ERR ly_set_rm_index_ordered(struct ly_set *set, uint32_t index, void (*destructor)(void *obj));
 
+/**
+ * @brief Frees data within leafref links record
+ *
+ * @param[in] rec The leafref links record
+ */
+void lyd_free_leafref_links_rec(struct lyd_leafref_links_rec *rec);
+
+/**
+ * @brief Frees all leafref nodes and target node of given data node
+ *
+ * @param[in] node The data node, which leafref nodes and/or target node should be cleared.
+ */
+void lyd_free_leafref_nodes(const struct lyd_node_term *node);
+
+/**
+ * @brief Gets or creates the leafref links record.
+ *
+ * @param[in] node The term data node.
+ * @param[out] record The leafref links record.
+ * @param[in] create Whether to create record if not exists.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+LY_ERR lyd_get_or_create_leafref_links_record(const struct lyd_node_term *node, struct lyd_leafref_links_rec **record, ly_bool create);
+
+/**
+ * @brief Adds links between leafref adn data node.
+ *
+ * If the links were already added, it will not be added again.
+ * This API requires usage of LY_CTX_LEAFREF_LINKING context flag.
+ *
+ * @param[in] node Data node to which, the leafref is pointing to.
+ * @param[in] leafref_node The leafref, which points to given node.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+LY_ERR lyd_link_leafref_node(const struct lyd_node_term *node, const struct lyd_node_term *leafref_node);
+
+/**
+ * @brief Removes links between leafref adn data node.
+ *
+ * If the links were never added, it will be silently ignored.
+ * This API requires usage of LY_CTX_LEAFREF_LINKING context flag.
+ *
+ * @param[in] node Data node to which, the leafref is pointing to.
+ * @param[in] leafref_node The leafref, which points to given node.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+LY_ERR lyd_unlink_leafref_node(const struct lyd_node_term *node, const struct lyd_node_term *leafref_node);
+
 #endif /* LY_TREE_DATA_INTERNAL_H_ */
diff --git a/src/tree_data_new.c b/src/tree_data_new.c
index 5d9f429..e56e61e 100644
--- a/src/tree_data_new.c
+++ b/src/tree_data_new.c
@@ -1315,6 +1315,11 @@
         val_change = 0;
     }
 
+    /* clear links to leafref nodes */
+    if (ly_ctx_get_options(LYD_CTX(term)) & LY_CTX_LEAFREF_LINKING) {
+        lyd_free_leafref_nodes(t);
+    }
+
     /* always clear the default flag */
     if (term->flags & LYD_DEFAULT) {
         for (parent = term; parent; parent = lyd_parent(parent)) {
diff --git a/src/tree_edit.h b/src/tree_edit.h
index 951d95d..113c9e3 100644
--- a/src/tree_edit.h
+++ b/src/tree_edit.h
@@ -232,6 +232,26 @@
         if (ARRAY){free((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1);}
 
 /**
+ * @brief Remove item from array based on value
+ *
+ * @param[in, out] ARRAY A ([sized array](@ref sizedarrays)) to be modified.
+ * @param[in] VALUE The item value to be removed. Only the first occurence will be removed.
+ */
+#define LY_ARRAY_REMOVE_VALUE(ARRAY, VALUE) \
+    { \
+        LY_ARRAY_COUNT_TYPE index__; \
+        LY_ARRAY_FOR(ARRAY, index__) { \
+            if (ARRAY[index__] == VALUE) { \
+                if (index__ != LY_ARRAY_COUNT(ARRAY) - 1) { \
+                    memmove(&(ARRAY[index__]), &(ARRAY[LY_ARRAY_COUNT(ARRAY) - 1]), sizeof *(ARRAY)); \
+                } \
+                LY_ARRAY_DECREMENT(ARRAY); \
+                break; \
+            } \
+        } \
+    }
+
+/**
  * @brief Insert item into linked list.
  *
  * @param[in,out] LIST Linked list to add to.
diff --git a/tests/utests/basic/test_context.c b/tests/utests/basic/test_context.c
index cfba1d3..d1fa2ad 100644
--- a/tests/utests/basic/test_context.c
+++ b/tests/utests/basic/test_context.c
@@ -220,6 +220,11 @@
     assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_PREFER_SEARCHDIRS));
     assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_PREFER_SEARCHDIRS);
 
+    /* LY_CTX_LEAFREF_LINKING */
+    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_LEAFREF_LINKING);
+    assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_LEAFREF_LINKING));
+    assert_int_equal(0, UTEST_LYCTX->flags & LY_CTX_LEAFREF_LINKING);
+
     assert_int_equal(UTEST_LYCTX->flags, ly_ctx_get_options(UTEST_LYCTX));
 
     /* set back */
@@ -243,6 +248,10 @@
     assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_PREFER_SEARCHDIRS));
     assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_PREFER_SEARCHDIRS);
 
+    /* LY_CTX_LEAFREF_LINKING */
+    assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_LINKING));
+    assert_int_not_equal(0, UTEST_LYCTX->flags & LY_CTX_LEAFREF_LINKING);
+
     assert_int_equal(UTEST_LYCTX->flags, ly_ctx_get_options(UTEST_LYCTX));
 }
 
diff --git a/tests/utests/data/test_tree_data.c b/tests/utests/data/test_tree_data.c
index 494fdf3..f022e3c 100644
--- a/tests/utests/data/test_tree_data.c
+++ b/tests/utests/data/test_tree_data.c
@@ -593,6 +593,97 @@
     vars = NULL;
 }
 
+static void
+test_data_leafref_nodes(void **state)
+{
+    struct lyd_node *tree, *iter;
+    struct lyd_node_term *target_node, *leafref_node;
+    const struct lyd_leafref_links_rec *rec;
+    const char *schema, *data, *value;
+
+    ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_LINKING);
+
+    schema =
+            "module test-data-hash {"
+            "  yang-version 1.1;"
+            "  namespace \"urn:tests:tdh\";"
+            "  prefix t;"
+            "  leaf-list ll {"
+            "    type string;"
+            "  }"
+            "  container c1 {"
+            "    leaf ref1 {"
+            "      type leafref {"
+            "        path \"../../ll\";"
+            "      }"
+            "    }"
+            "  }"
+            "  leaf ref2 {"
+            "    type leafref {"
+            "      path \"../ll\";"
+            "    }"
+            "  }"
+            "}";
+
+    UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL);
+
+    data =
+            "{"
+            "  \"test-data-hash:ll\": [\"qwe\", \"asd\"],"
+            "  \"test-data-hash:c1\": { \"ref1\": \"qwe\"},"
+            "  \"test-data-hash:ref2\": \"asd\""
+            "}";
+
+    /* The run must not crash due to the assert that checks the hash. */
+    CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree);
+    LY_LIST_FOR(tree, iter) {
+        if (strcmp(iter->schema->name, "ll") == 0) {
+            value = lyd_get_value(iter);
+            if (strcmp(value, "asd") == 0) {
+                target_node = (struct lyd_node_term *)iter;
+            }
+        }
+        if (strcmp(iter->schema->name, "ref2") == 0) {
+            leafref_node = (struct lyd_node_term *)iter;
+        }
+    }
+
+    /* verify state after leafref plugin validation */
+    assert_int_equal(LY_SUCCESS, lyd_leafref_get_links(target_node, &rec));
+    assert_int_equal(1, LY_ARRAY_COUNT(rec->leafref_nodes));
+    assert_int_equal(LY_SUCCESS, lyd_leafref_get_links(leafref_node, &rec));
+    assert_ptr_equal(rec->target_node, target_node);
+    /* value modification of target */
+    assert_int_equal(LY_SUCCESS, lyd_change_term((struct lyd_node *)target_node, "ASD"));
+    assert_int_equal(LY_ENOTFOUND, lyd_leafref_get_links(target_node, &rec));
+    assert_int_equal(LY_ENOTFOUND, lyd_leafref_get_links(leafref_node, &rec));
+    /* change back to original value */
+    assert_int_equal(LY_SUCCESS, lyd_change_term((struct lyd_node *)target_node, "asd"));
+    assert_int_equal(LY_ENOTFOUND, lyd_leafref_get_links(target_node, &rec));
+    assert_int_equal(LY_ENOTFOUND, lyd_leafref_get_links(leafref_node, &rec));
+    /* linking the whole tree again */
+    assert_int_equal(LY_SUCCESS, lyd_leafref_link_node_tree(tree));
+    assert_int_equal(LY_SUCCESS, lyd_leafref_get_links(target_node, &rec));
+    assert_int_equal(1, LY_ARRAY_COUNT(rec->leafref_nodes));
+    assert_int_equal(LY_SUCCESS, lyd_leafref_get_links(leafref_node, &rec));
+    assert_ptr_equal(rec->target_node, target_node);
+    /* value modification of leafref */
+    assert_int_equal(LY_SUCCESS, lyd_change_term((struct lyd_node *)leafref_node, "qwe"));
+    assert_int_equal(LY_ENOTFOUND, lyd_leafref_get_links(target_node, &rec));
+    assert_int_equal(LY_ENOTFOUND, lyd_leafref_get_links(leafref_node, &rec));
+    assert_int_equal(LY_SUCCESS, lyd_change_term((struct lyd_node *)leafref_node, "asd"));
+    assert_int_equal(LY_ENOTFOUND, lyd_leafref_get_links(target_node, &rec));
+    assert_int_equal(LY_ENOTFOUND, lyd_leafref_get_links(leafref_node, &rec));
+    /* linking the whole tree again */
+    assert_int_equal(LY_SUCCESS, lyd_leafref_link_node_tree(tree));
+    assert_int_equal(LY_SUCCESS, lyd_leafref_get_links(target_node, &rec));
+    assert_int_equal(1, LY_ARRAY_COUNT(rec->leafref_nodes));
+    assert_int_equal(LY_SUCCESS, lyd_leafref_get_links(leafref_node, &rec));
+    assert_ptr_equal(rec->target_node, target_node);
+    /* freeing whole tree */
+    lyd_free_all(tree);
+}
+
 int
 main(void)
 {
@@ -606,6 +697,7 @@
         UTEST(test_find_path, setup),
         UTEST(test_data_hash, setup),
         UTEST(test_lyxp_vars),
+        UTEST(test_data_leafref_nodes),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);