context UPDATE run-time plugin support (#2213)

* Adding context based extensions plugins support

This patch adds ability to load plugins directly from memory without
need to create shared library by using lyplg_add_plugin() API.

It also allows to associate plugin directly with context, so given
plugin will not affect all contexts, just given context

* Refactored based on PR comments
diff --git a/src/context.c b/src/context.c
index e023f31..307dde7 100644
--- a/src/context.c
+++ b/src/context.c
@@ -1384,7 +1384,11 @@
     /* LYB hash lock */
     pthread_mutex_destroy(&ctx->lyb_hash_lock);
 
-    /* plugins - will be removed only if this is the last context */
+    /* context specific plugins */
+    ly_set_erase(&ctx->plugins_types, NULL);
+    ly_set_erase(&ctx->plugins_extensions, NULL);
+
+    /* shared plugins - will be removed only if this is the last context */
     lyplg_clean();
 
     free(ctx);
diff --git a/src/ly_common.h b/src/ly_common.h
index 00ac506..ec0d3ea 100644
--- a/src/ly_common.h
+++ b/src/ly_common.h
@@ -356,6 +356,8 @@
     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 */
+    struct ly_set plugins_types;      /**< context specific set of type plugins */
+    struct ly_set plugins_extensions; /**< contets specific set of extension plugins */
 };
 
 /**
diff --git a/src/plugins.c b/src/plugins.c
index ab264f0..87273fa 100644
--- a/src/plugins.c
+++ b/src/plugins.c
@@ -130,21 +130,22 @@
 /**
  * @brief Iterate over list of loaded plugins of the given @p type.
  *
+ * @param[in] ctx The context for which the plugin is searched for
  * @param[in] type Type of the plugins to iterate.
  * @param[in,out] index The iterator - set to 0 for the first call.
  * @return The plugin records, NULL if no more record is available.
  */
 static struct lyplg_record *
-plugins_iter(enum LYPLG type, uint32_t *index)
+plugins_iter(const struct ly_ctx *ctx, enum LYPLG type, uint32_t *index)
 {
-    struct ly_set *plugins;
+    const struct ly_set *plugins;
 
     assert(index);
 
     if (type == LYPLG_EXTENSION) {
-        plugins = &plugins_extensions;
+        plugins = ctx ? &ctx->plugins_extensions : &plugins_extensions;
     } else {
-        plugins = &plugins_types;
+        plugins = ctx ? &ctx->plugins_types : &plugins_types;
     }
 
     if (*index == plugins->count) {
@@ -156,7 +157,7 @@
 }
 
 static void *
-lyplg_record_find(enum LYPLG type, const char *module, const char *revision, const char *name)
+lyplg_record_find(const struct ly_ctx *ctx, enum LYPLG type, const char *module, const char *revision, const char *name)
 {
     uint32_t i = 0;
     struct lyplg_record *item;
@@ -164,7 +165,7 @@
     assert(module);
     assert(name);
 
-    while ((item = plugins_iter(type, &i)) != NULL) {
+    while ((item = plugins_iter(ctx, type, &i)) != NULL) {
         if (!strcmp(item->module, module) && !strcmp(item->name, name)) {
             if (item->revision && revision && strcmp(item->revision, revision)) {
                 continue;
@@ -180,23 +181,47 @@
 }
 
 struct lyplg_type *
-lyplg_type_plugin_find(const char *module, const char *revision, const char *name)
+lyplg_type_plugin_find(const struct ly_ctx *ctx, const char *module, const char *revision, const char *name)
 {
-    struct lyplg_record *record;
+    struct lyplg_record *record = NULL;
 
-    record = lyplg_record_find(LYPLG_TYPE, module, revision, name);
+    if (ctx) {
+        /* try to find context specific plugin */
+        record = lyplg_record_find(ctx, LYPLG_TYPE, module, revision, name);
+    }
+
+    if (!record) {
+        /* try to find shared plugin */
+        record = lyplg_record_find(NULL, LYPLG_TYPE, module, revision, name);
+    }
+
     return record ? &((struct lyplg_type_record *)record)->plugin : NULL;
 }
 
 struct lyplg_ext_record *
-lyplg_ext_record_find(const char *module, const char *revision, const char *name)
+lyplg_ext_record_find(const struct ly_ctx *ctx, const char *module, const char *revision, const char *name)
 {
-    return lyplg_record_find(LYPLG_EXTENSION, module, revision, name);
+    struct lyplg_ext_record *record = NULL;
+
+    if (ctx) {
+        /* try to find context specific plugin */
+        record = lyplg_record_find(ctx, LYPLG_EXTENSION, module, revision, name);
+    }
+
+    if (!record) {
+        /* try to find shared plugin */
+        record = lyplg_record_find(NULL, LYPLG_EXTENSION, module, revision, name);
+    }
+
+    return record;
 }
 
 /**
  * @brief Insert the provided extension plugin records into the internal set of extension plugins for use by libyang.
  *
+ * @param[in] ctx The context to which the plugin should be associated with. If NULL, the plugin is considered to be shared
+ * between all existing contexts.
+ * @param[in] type The type of plugins records
  * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed
  * record.
  * @return LY_SUCCESS in case of success
@@ -204,8 +229,10 @@
  * @return LY_EMEM in case of memory allocation failure.
  */
 static LY_ERR
-plugins_insert(enum LYPLG type, const void *recs)
+plugins_insert(struct ly_ctx *ctx, enum LYPLG type, const void *recs)
 {
+    struct ly_set *plugins;
+
     if (!recs) {
         return LY_SUCCESS;
     }
@@ -213,14 +240,18 @@
     if (type == LYPLG_EXTENSION) {
         const struct lyplg_ext_record *rec = (const struct lyplg_ext_record *)recs;
 
+        plugins = ctx ? &ctx->plugins_extensions : &plugins_extensions;
+
         for (uint32_t i = 0; rec[i].name; i++) {
-            LY_CHECK_RET(ly_set_add(&plugins_extensions, (void *)&rec[i], 0, NULL));
+            LY_CHECK_RET(ly_set_add(plugins, (void *)&rec[i], 0, NULL));
         }
     } else { /* LYPLG_TYPE */
         const struct lyplg_type_record *rec = (const struct lyplg_type_record *)recs;
 
+        plugins = ctx ? &ctx->plugins_types : &plugins_types;
+
         for (uint32_t i = 0; rec[i].name; i++) {
-            LY_CHECK_RET(ly_set_add(&plugins_types, (void *)&rec[i], 0, NULL));
+            LY_CHECK_RET(ly_set_add(plugins, (void *)&rec[i], 0, NULL));
         }
     }
 
@@ -329,7 +360,7 @@
         }
 
         /* ... and load all the types plugins */
-        LY_CHECK_RET(plugins_insert(type, plugins));
+        LY_CHECK_RET(plugins_insert(NULL, type, plugins));
     }
 
     return LY_SUCCESS;
@@ -457,48 +488,48 @@
     }
 
     /* internal types */
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_binary), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_bits), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_boolean), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_decimal64), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_empty), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_enumeration), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_identityref), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_integer), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_leafref), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_string), error);
-    LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_union), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_binary), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_bits), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_boolean), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_decimal64), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_empty), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_enumeration), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_identityref), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_instanceid), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_integer), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_leafref), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_string), error);
+    LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_union), error);
 
     if (!builtin_type_plugins_only) {
         /* yang */
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid_keys), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_instanceid_keys), error);
 
         /* ietf-inet-types */
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address_no_zone), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address_no_zone), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_prefix), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_prefix), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_ipv4_address), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_ipv4_address_no_zone), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_ipv6_address), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_ipv6_address_no_zone), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_ipv4_prefix), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_ipv6_prefix), error);
 
         /* ietf-yang-types */
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_date_and_time), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_hex_string), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_xpath10), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_date_and_time), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_hex_string), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_xpath10), error);
 
         /* ietf-netconf-acm */
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_node_instanceid), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_node_instanceid), error);
 
         /* lyds_tree */
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_lyds_tree), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_TYPE, plugins_lyds_tree), error);
 
         /* internal extensions */
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_metadata), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_nacm), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_yangdata), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_schema_mount), error);
-        LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_structure), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_EXTENSION, plugins_metadata), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_EXTENSION, plugins_nacm), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_EXTENSION, plugins_yangdata), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_EXTENSION, plugins_schema_mount), error);
+        LY_CHECK_GOTO(ret = plugins_insert(NULL, LYPLG_EXTENSION, plugins_structure), error);
     }
 
 #ifndef STATIC
@@ -562,3 +593,60 @@
     return ret;
 #endif
 }
+
+/**
+ * @brief Manually load an extension plugins from memory
+ *
+ * Note, that a plugin can be loaded only if there is at least one context. The loaded plugins are connected with the
+ * existence of a context. When all the contexts are destroyed, all the plugins are unloaded.
+ *
+ * @param[in] ctx The context to which the plugin should be associated with. If NULL, the plugin is considered to be shared
+ * between all existing contexts.
+ * @param[in] version The version of plugin records.
+ * @param[in] type The type of plugins records.
+ * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed
+ * record.
+ *
+ * @return LY_SUCCESS if the plugins with compatible version were successfully loaded.
+ * @return LY_EDENIED in case there is no context and the plugin cannot be loaded.
+ * @return LY_EINVAL when recs is NULL or the plugin contains invalid content for this libyang version.
+ */
+static LY_ERR
+lyplg_add_plugin(struct ly_ctx *ctx, uint32_t version, enum LYPLG type, const void *recs)
+{
+    LY_ERR ret = LY_SUCCESS;
+
+    LY_CHECK_ARG_RET(NULL, recs, LY_EINVAL);
+
+    if (version != plugins_load_info[type].apiver) {
+        LOGERR(ctx, LY_EINVAL, "Adding user %s plugin failed, wrong API version - %d expected, %d found.",
+                plugins_load_info[type].id, plugins_load_info[type].apiver, version);
+        return LY_EINVAL;
+    }
+
+    /* works only in case a context exists */
+    pthread_mutex_lock(&plugins_guard);
+    if (!context_refcount) {
+        /* no context */
+        pthread_mutex_unlock(&plugins_guard);
+        LOGERR(NULL, LY_EDENIED, "To add a plugin, at least one context must exists.");
+        return LY_EDENIED;
+    }
+
+    plugins_insert(ctx, type, recs);
+    pthread_mutex_unlock(&plugins_guard);
+
+    return ret;
+}
+
+LIBYANG_API_DEF LY_ERR
+lyplg_add_extension_plugin(struct ly_ctx *ctx, uint32_t version, const struct lyplg_ext_record *recs)
+{
+    return lyplg_add_plugin(ctx, version, LYPLG_EXTENSION, recs);
+}
+
+LIBYANG_API_DEF LY_ERR
+lyplg_add_type_plugin(struct ly_ctx *ctx, uint32_t version, const struct lyplg_type_record *recs)
+{
+    return lyplg_add_plugin(ctx, version, LYPLG_TYPE, recs);
+}
diff --git a/src/plugins.h b/src/plugins.h
index f868879..3ffc7cf 100644
--- a/src/plugins.h
+++ b/src/plugins.h
@@ -17,6 +17,9 @@
 
 #include "log.h"
 
+struct lyplg_ext_record;
+struct lyplg_type_record;
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -85,6 +88,41 @@
  */
 LIBYANG_API_DECL LY_ERR lyplg_add(const char *pathname);
 
+/**
+ * @brief Manually load extension plugins from memory
+ *
+ * Note, that a plugin can be loaded only if there is at least one context. The loaded plugins are connected with the
+ * existence of a context. When all the contexts are destroyed, all the plugins are unloaded.
+ *
+ * @param[in] ctx The context to which the plugin should be associated with. If NULL, the plugin is considered to be shared
+ * between all existing contexts.
+ * @param[in] version The version of plugin records.
+ * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed
+ * record.
+ *
+ * @return LY_SUCCESS if the plugins with compatible version were successfully loaded.
+ * @return LY_EDENIED in case there is no context and the plugin cannot be loaded.
+ * @return LY_EINVAL when recs is NULL or the plugin contains invalid content for this libyang version.
+ */
+LIBYANG_API_DECL LY_ERR lyplg_add_extension_plugin(struct ly_ctx *ctx, uint32_t version, const struct lyplg_ext_record *recs);
+
+/**
+ * @brief Manually load type plugins from memory
+ *
+ * Note, that a plugin can be loaded only if there is at least one context. The loaded plugins are connected with the
+ * existence of a context. When all the contexts are destroyed, all the plugins are unloaded.
+ *
+ * @param[in] ctx The context to which the plugin should be associated with. If NULL, the plugin is considered to be shared
+ * between all existing contexts.
+ * @param[in] version The version of plugin records.
+ * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed
+ * record.
+ *
+ * @return LY_SUCCESS if the plugins with compatible version were successfully loaded.
+ * @return LY_EDENIED in case there is no context and the plugin cannot be loaded.
+ * @return LY_EINVAL when recs is NULL or the plugin contains invalid content for this libyang version.
+ */
+LIBYANG_API_DECL LY_ERR lyplg_add_type_plugin(struct ly_ctx *ctx, uint32_t version, const struct lyplg_type_record *recs);
 /** @} plugins */
 
 #ifdef __cplusplus
diff --git a/src/plugins_internal.h b/src/plugins_internal.h
index 59adacf..2ba54f1 100644
--- a/src/plugins_internal.h
+++ b/src/plugins_internal.h
@@ -63,6 +63,7 @@
 /**
  * @brief Find a type plugin.
  *
+ * @param[in] ctx The optional context for which the plugin should be find. If NULL, only shared plugins will be searched
  * @param[in] module Name of the module where the type is defined. Must not be NULL, in case of plugins for
  * built-in types, the module is "".
  * @param[in] revision Revision of the module for which the plugin is implemented. NULL is not a wildcard, it matches
@@ -70,17 +71,18 @@
  * @param[in] name Name of the type which the plugin implements.
  * @return Found type plugin, NULL if none found.
  */
-struct lyplg_type *lyplg_type_plugin_find(const char *module, const char *revision, const char *name);
+struct lyplg_type *lyplg_type_plugin_find(const struct ly_ctx *ctx, const char *module, const char *revision, const char *name);
 
 /**
  * @brief Find an extension plugin.
  *
+ * @param[in] ctx The optional context for which the plugin should be find. If NULL, only shared plugins will be searched
  * @param[in] module Name of the module where the extension is defined.
  * @param[in] revision Revision of the module for which the plugin is implemented. NULL is not a wildcard, it matches
  * only the plugins with NULL revision specified.
  * @param[in] name Name of the extension which the plugin implements.
  * @return Found extension record, NULL if none found.
  */
-struct lyplg_ext_record *lyplg_ext_record_find(const char *module, const char *revision, const char *name);
+struct lyplg_ext_record *lyplg_ext_record_find(const struct ly_ctx *ctx, const char *module, const char *revision, const char *name);
 
 #endif /* LY_PLUGINS_INTERNAL_H_ */
diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c
index 4187575..53e1bed 100644
--- a/src/schema_compile_node.c
+++ b/src/schema_compile_node.c
@@ -2263,7 +2263,7 @@
         }
 
         /* try to find loaded user type plugins */
-        plugin = lyplg_type_plugin_find(tctx->tpdf->type.pmod->mod->name, tctx->tpdf->type.pmod->mod->revision,
+        plugin = lyplg_type_plugin_find(ctx->ctx, tctx->tpdf->type.pmod->mod->name, tctx->tpdf->type.pmod->mod->revision,
                 tctx->tpdf->name);
         if (!plugin && base) {
             /* use the base type implementation if available */
@@ -2271,7 +2271,7 @@
         }
         if (!plugin) {
             /* use the internal built-in type implementation */
-            plugin = lyplg_type_plugin_find("", NULL, ly_data_type2str[basetype]);
+            plugin = lyplg_type_plugin_find(ctx->ctx, "", NULL, ly_data_type2str[basetype]);
         }
         assert(plugin);
 
@@ -2312,7 +2312,7 @@
     /* process the type definition in leaf */
     if (type_p->flags || type_p->exts || !base || (basetype == LY_TYPE_LEAFREF)) {
         /* leaf type has changes that need to be compiled into the type */
-        plugin = base ? base->plugin : lyplg_type_plugin_find("", NULL, ly_data_type2str[basetype]);
+        plugin = base ? base->plugin : lyplg_type_plugin_find(ctx->ctx, "", NULL, ly_data_type2str[basetype]);
         ret = lys_compile_type_(ctx, context_pnode, context_flags, context_name, (struct lysp_type *)type_p, basetype,
                 NULL, base, plugin, &tpdf_chain, 0, type);
         LY_CHECK_GOTO(ret, cleanup);
diff --git a/src/tree_schema.c b/src/tree_schema.c
index e8b9857..9d9d2b4 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -1362,7 +1362,7 @@
             LY_CHECK_RET(lysp_ext_instance_resolve_argument(PARSER_CTX(pctx), ext));
 
             /* find the extension record, if any */
-            ext->record = lyplg_ext_record_find(mod->name, mod->revision, ext->def->name);
+            ext->record = lyplg_ext_record_find(mod->ctx, mod->name, mod->revision, ext->def->name);
         }
     }
 
diff --git a/tests/utests/basic/test_plugins.c b/tests/utests/basic/test_plugins.c
index cf2e87b..df7523a 100644
--- a/tests/utests/basic/test_plugins.c
+++ b/tests/utests/basic/test_plugins.c
@@ -54,12 +54,12 @@
     leaf = (struct lysc_node_leaf *)mod->compiled->data;
     assert_int_equal(LYS_LEAF, leaf->nodetype);
 
-    assert_non_null(plugin_t = lyplg_type_plugin_find("libyang-plugins-simple", NULL, "note"));
+    assert_non_null(plugin_t = lyplg_type_plugin_find(NULL, "libyang-plugins-simple", NULL, "note"));
     assert_string_equal("ly2 simple test v1", plugin_t->id);
     assert_ptr_equal(leaf->type->plugin, plugin_t);
 
     assert_int_equal(1, LY_ARRAY_COUNT(leaf->exts));
-    assert_non_null(record_e = lyplg_ext_record_find("libyang-plugins-simple", NULL, "hint"));
+    assert_non_null(record_e = lyplg_ext_record_find(NULL, "libyang-plugins-simple", NULL, "hint"));
     assert_string_equal("ly2 simple test v1", record_e->plugin.id);
     assert_ptr_equal(leaf->exts[0].def->plugin, &record_e->plugin);
 
@@ -96,6 +96,49 @@
     lyd_free_all(tree);
 }
 
+static LY_ERR
+parse_clb(struct lysp_ctx *UNUSED(pctx), struct lysp_ext_instance *ext)
+{
+    struct lysp_node_leaf *leaf;
+
+    leaf = (struct lysp_node_leaf *)ext->parent;
+    leaf->flags |= LYS_STATUS_OBSLT;
+    return LY_SUCCESS;
+}
+
+struct lyplg_ext_record memory_recs[] = {
+    {
+        .module = "libyang-plugins-simple",
+        .revision = NULL,
+        .name = "hint",
+
+        .plugin.id = "memory-plugin-v1",
+        .plugin.parse = parse_clb,
+        .plugin.compile = NULL,
+        .plugin.printer_info = NULL,
+        .plugin.node = NULL,
+        .plugin.snode = NULL,
+        .plugin.validate = NULL,
+        .plugin.pfree = NULL,
+        .plugin.cfree = NULL
+    },
+    {0} /* terminating zeroed item */
+};
+
+static void
+test_simple_from_memory(void **state)
+{
+    struct lys_module *mod;
+    struct lysc_node_leaf *leaf;
+
+    lyplg_add_extension_plugin(UTEST_LYCTX, LYPLG_EXT_API_VERSION, memory_recs);
+    UTEST_ADD_MODULE(simple, LYS_IN_YANG, NULL, &mod);
+
+    leaf = (struct lysc_node_leaf *)mod->compiled->data;
+    assert_int_equal(LYS_LEAF, leaf->nodetype);
+    assert_true(leaf->flags & LYS_STATUS_OBSLT);
+}
+
 int
 main(void)
 {
@@ -103,6 +146,7 @@
         UTEST(test_add_invalid),
         UTEST(test_add_simple),
         UTEST(test_not_implemented),
+        UTEST(test_simple_from_memory),
     };
 
     return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/utests/types/binary.c b/tests/utests/types/binary.c
index 910f756..4d0cfbc 100644
--- a/tests/utests/types/binary.c
+++ b/tests/utests/types/binary.c
@@ -51,7 +51,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value value = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BINARY]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_BINARY]);
     struct lysc_type *lysc_type, *lysc_type2;
     LY_ERR ly_ret;
     const char *schema;
@@ -250,7 +250,7 @@
     struct lyd_value value = {0};
     struct lys_module *mod;
     struct lysc_type *lysc_type;
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BINARY]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_BINARY]);
     struct ly_err_item *err = NULL;
 
     /* create schema. Prepare common used variables */
@@ -273,7 +273,7 @@
     struct lyd_value value = {0}, dup;
     struct lys_module *mod;
     struct lysc_type *lysc_type;
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BINARY]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_BINARY]);
     struct ly_err_item *err = NULL;
 
     /* create schema. Prepare common used variables */
@@ -298,7 +298,7 @@
     const char *schema;
     struct lys_module *mod;
     struct lyd_value val1 = {0}, val2 = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BINARY]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_BINARY]);
     struct lysc_type *lysc_type;
     struct ly_err_item *err = NULL;
 
diff --git a/tests/utests/types/bits.c b/tests/utests/types/bits.c
index ddf8143..3555377 100644
--- a/tests/utests/types/bits.c
+++ b/tests/utests/types/bits.c
@@ -800,7 +800,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value value = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BITS]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_BITS]);
     struct lysc_type *lysc_type;
     struct lysc_type lysc_type_test;
     LY_ERR ly_ret;
@@ -894,7 +894,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value values[10];
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BITS]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_BITS]);
     struct lysc_type *lysc_type;
     LY_ERR ly_ret;
     const char *schema;
@@ -956,7 +956,7 @@
 {
     const char *schema;
     struct lys_module *mod;
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BITS]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_BITS]);
     struct lysc_type *lysc_type;
     struct ly_err_item *err = NULL;
     struct lyd_value val1 = {0}, val2 = {0};
@@ -993,7 +993,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value values[10];
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BITS]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_BITS]);
     struct lysc_type *lysc_type;
     LY_ERR ly_ret;
     const char *schema;
@@ -1035,7 +1035,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value values[10];
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BITS]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_BITS]);
     struct lysc_type *lysc_type;
     const char *schema;
     LY_ERR ly_ret;
diff --git a/tests/utests/types/enumeration.c b/tests/utests/types/enumeration.c
index 04bbae2..0d42c54 100644
--- a/tests/utests/types/enumeration.c
+++ b/tests/utests/types/enumeration.c
@@ -94,7 +94,7 @@
     const char *schema;
     struct lys_module *mod;
     struct lyd_value val1 = {0}, val2 = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_ENUM]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_ENUM]);
     struct lysc_type *lysc_type;
     struct ly_err_item *err = NULL;
 
diff --git a/tests/utests/types/inet_types.c b/tests/utests/types/inet_types.c
index 016d5bb..402cade 100644
--- a/tests/utests/types/inet_types.c
+++ b/tests/utests/types/inet_types.c
@@ -196,7 +196,7 @@
     const char *schema;
     struct lys_module *mod;
     struct lyd_value val1 = {0}, val2 = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_UNION]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_UNION]);
     struct lysc_type *lysc_type;
     struct ly_err_item *err = NULL;
 
@@ -238,7 +238,7 @@
 
     /* ipv6-address */
     lysc_type = ((struct lysc_node_leaflist *)mod->compiled->data->next)->type;
-    type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_STRING]);
+    type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_STRING]);
 
     v1 = "2008:15:0:0:0:0:feAC:1";
     assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type, v1, strlen(v1),
@@ -266,7 +266,7 @@
 
     /* ipv4-address-no-zone */
     lysc_type = ((struct lysc_node_leaflist *)mod->compiled->data->next->next)->type;
-    type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_UNION]);
+    type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_UNION]);
     v1 = "127.0.0.1";
     assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type, v1, strlen(v1),
             0, LY_VALUE_JSON, NULL, LYD_VALHINT_STRING, NULL, &val1, NULL, &err));
@@ -281,7 +281,7 @@
 
     /* ipv6-address-no-zone */
     lysc_type = ((struct lysc_node_leaflist *)mod->compiled->data->next->next->next)->type;
-    type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_STRING]);
+    type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_STRING]);
     v1 = "A:B:c:D:e:f:1:1";
     assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type, v1, strlen(v1),
             0, LY_VALUE_JSON, NULL, LYD_VALHINT_STRING, NULL, &val1, NULL, &err));
diff --git a/tests/utests/types/int8.c b/tests/utests/types/int8.c
index d92d146..dff29ef 100644
--- a/tests/utests/types/int8.c
+++ b/tests/utests/types/int8.c
@@ -1399,7 +1399,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value value = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_INT8]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_INT8]);
     struct lysc_type *lysc_type;
     LY_ERR ly_ret;
     char *alloc;
@@ -1546,7 +1546,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value values[10];
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_INT8]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_INT8]);
     struct lysc_type *lysc_type;
     LY_ERR ly_ret;
     const char *schema;
@@ -1631,7 +1631,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value values[10];
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_INT8]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_INT8]);
     struct lysc_type *lysc_type;
     LY_ERR ly_ret;
     const char *schema;
@@ -1671,7 +1671,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value values[10];
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_INT8]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_INT8]);
     struct lysc_type *lysc_type[2];
     const char *schema;
     LY_ERR ly_ret;
diff --git a/tests/utests/types/leafref.c b/tests/utests/types/leafref.c
index 1941e48..81e8ded 100644
--- a/tests/utests/types/leafref.c
+++ b/tests/utests/types/leafref.c
@@ -216,7 +216,7 @@
     const char *schema;
     struct lys_module *mod;
     struct lyd_value val1 = {0}, val2 = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_LEAFREF]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_LEAFREF]);
     struct lysc_type *lysc_type;
     struct ly_err_item *err = NULL;
 
diff --git a/tests/utests/types/string.c b/tests/utests/types/string.c
index f5aec34..2f002b7 100644
--- a/tests/utests/types/string.c
+++ b/tests/utests/types/string.c
@@ -1060,7 +1060,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value value = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_STRING]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_STRING]);
     struct lysc_type *lysc_type;
     char *alloc_text;
     unsigned int alloc_text_size;
@@ -1201,7 +1201,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value values[10];
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_STRING]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_STRING]);
     struct lysc_type *lysc_type;
     LY_ERR ly_ret;
     const char *schema;
@@ -1259,7 +1259,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value values[10];
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_STRING]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_STRING]);
     struct lysc_type *lysc_type;
     LY_ERR ly_ret;
 
@@ -1298,7 +1298,7 @@
     struct ly_err_item *err = NULL;
     struct lys_module *mod;
     struct lyd_value values[10];
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_STRING]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_STRING]);
     struct lysc_type *lysc_type[2];
     const char *schema;
     LY_ERR ly_ret;
diff --git a/tests/utests/types/union.c b/tests/utests/types/union.c
index 2c378df..0d6ad21 100644
--- a/tests/utests/types/union.c
+++ b/tests/utests/types/union.c
@@ -154,7 +154,7 @@
     const char *schema;
     struct lys_module *mod;
     struct lyd_value val1 = {0}, val2 = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_UNION]);
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[LY_TYPE_UNION]);
     struct lysc_type *lysc_type;
     struct ly_err_item *err = NULL;
 
diff --git a/tests/utests/types/yang_types.c b/tests/utests/types/yang_types.c
index 51a7c7f..42e1f32 100644
--- a/tests/utests/types/yang_types.c
+++ b/tests/utests/types/yang_types.c
@@ -235,7 +235,7 @@
     const char *schema;
     struct lys_module *mod;
     struct lyd_value val1 = {0}, val2 = {0};
-    struct lyplg_type *type = lyplg_type_plugin_find("ietf-yang-types", "2013-07-15", "date-and-time");
+    struct lyplg_type *type = lyplg_type_plugin_find(NULL, "ietf-yang-types", "2013-07-15", "date-and-time");
     struct lysc_type *lysc_type;
     struct ly_err_item *err = NULL;
 
diff --git a/tests/utests/utests.h b/tests/utests/utests.h
index 6824db4..7b8107f 100644
--- a/tests/utests/utests.h
+++ b/tests/utests/utests.h
@@ -231,7 +231,7 @@
     assert_non_null(NODE); \
     assert_int_equal((NODE)->basetype, TYPE); \
     CHECK_ARRAY((NODE)->exts, EXTS); \
-    assert_ptr_equal((NODE)->plugin, lyplg_type_plugin_find("", NULL, ly_data_type2str[TYPE]))
+    assert_ptr_equal((NODE)->plugin, lyplg_type_plugin_find(NULL, "", NULL, ly_data_type2str[TYPE]))
 
 /**
  * @brief check compileted numeric type