From 7a02b8a708a5bc39c0825c9e5cd2596fffc0dcb0 Mon Sep 17 00:00:00 2001 From: Ilias Aberkane Date: Thu, 24 Sep 2026 14:54:11 +0200 Subject: [PATCH] plugins exts BUGFIX find parsed ext instance in submodules lyplg_ext_parsed_get_storage() searches only the parsed extension instances of the module itself. However, top-level extension instances of a submodule are compiled into the main module's compiled extension array, so their parsed counterparts live in the submodule's parsed extension array and the lookup failed, hitting the assert. This could be triggered, for example, by a submodule top-level sx:augment-structure instance when printing the module tree with compiled nodes (LY_CTX_SET_PRIV_PARSED) or when resolving typedefs of extension instance children during compilation. Search the parsed extension arrays of all included submodules as a fallback. --- src/plugins_exts.c | 21 +++++++++- tests/utests/extensions/test_structure.c | 49 ++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/plugins_exts.c b/src/plugins_exts.c index d796a68225..a19ef20835 100644 --- a/src/plugins_exts.c +++ b/src/plugins_exts.c @@ -559,15 +559,17 @@ lyplg_ext_get_storage(const struct lysc_ext_instance *ext, int stmt, uint32_t st LIBYANG_API_DEF LY_ERR lyplg_ext_parsed_get_storage(const struct lysc_ext_instance *ext, int stmt, uint32_t storage_size, const void **storage) { - LY_ARRAY_COUNT_TYPE u; + LY_ARRAY_COUNT_TYPE u, v; const struct lysp_ext_instance *extp = NULL; + const struct lysp_submodule *submod; const char *extp_name; enum ly_stmt match = 0; void **s_p = NULL; LY_CHECK_ARG_RET(NULL, ext, ext->module->parsed, LY_EINVAL); - /* find the parsed ext instance */ + /* find the parsed ext instance, it may be a top-level extension instance of the module or + * any of its submodules */ LY_ARRAY_FOR(ext->module->parsed->exts, u) { extp = &ext->module->parsed->exts[u]; extp_name = strchr(extp->name, ':') + 1; @@ -577,6 +579,21 @@ lyplg_ext_parsed_get_storage(const struct lysc_ext_instance *ext, int stmt, uint } extp = NULL; } + for (v = 0; !extp && (v < LY_ARRAY_COUNT(ext->module->parsed->includes)); ++v) { + submod = ext->module->parsed->includes[v].submodule; + if (!submod) { + continue; + } + LY_ARRAY_FOR(submod->exts, u) { + extp = &submod->exts[u]; + extp_name = strchr(extp->name, ':') + 1; + + if (!strcmp(ext->def->name, extp_name)) { + break; + } + extp = NULL; + } + } assert(extp); if (!(stmt & LY_STMT_NODE_MASK)) { diff --git a/tests/utests/extensions/test_structure.c b/tests/utests/extensions/test_structure.c index d8bea8ea99..318a5a7992 100644 --- a/tests/utests/extensions/test_structure.c +++ b/tests/utests/extensions/test_structure.c @@ -448,6 +448,54 @@ test_xpath(void **state) lyd_free_all(tree); } +static LY_ERR +submod_imp_clb(const char *UNUSED(mod_name), const char *UNUSED(mod_rev), const char *submod_name, + const char *UNUSED(sub_rev), void *user_data, LYS_INFORMAT *format, const char **module_data, + void (**free_module_data)(void *model_data, void *user_data)) +{ + if (!submod_name || strcmp(submod_name, "c_sub")) { + return LY_ENOTFOUND; + } + + *module_data = user_data; + *format = LYS_IN_YANG; + *free_module_data = NULL; + return LY_SUCCESS; +} + +static void +test_submod(void **state) +{ + struct lys_module *mod; + char *printed = NULL; + const char *data, *submod; + + /* top-level extension instances of a submodule are compiled into the module + * but their parsed instances are stored in the submodule */ + submod = "submodule c_sub {yang-version 1.1; belongs-to c {prefix c;}" + "import ietf-yang-structure-ext {prefix sx;}" + "sx:augment-structure \"/c:basetop/c:x\" {leaf z {type string;}}" + "}"; + ly_ctx_set_module_imp_clb(UTEST_LYCTX, submod_imp_clb, (void *)submod); + + data = "module c {yang-version 1.1; namespace urn:tests:extensions:structure:c; prefix c;" + "include c_sub;" + "import ietf-yang-structure-ext {prefix sx;}" + "sx:structure basetop {container x {leaf l {type string;}}}" + "}"; + UTEST_ADD_MODULE(data, LYS_IN_YANG, NULL, &mod); + + ly_ctx_set_module_imp_clb(UTEST_LYCTX, NULL, NULL); + + /* tree print with compiled nodes iterates compiled extension instances, the parsed one + * of a submodule extension instance used not to be found and asserted on */ + assert_int_equal(LY_SUCCESS, ly_ctx_set_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED)); + assert_int_equal(LY_SUCCESS, lys_print_mem(&printed, mod, LYS_OUT_TREE, 0)); + assert_int_equal(LY_SUCCESS, ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED)); + assert_non_null(strstr(printed, "augment-structure /c:basetop/c:x:")); + free(printed); +} + int main(void) { @@ -456,6 +504,7 @@ main(void) UTEST(test_schema_invalid), UTEST(test_parse), UTEST(test_xpath), + UTEST(test_submod), }; return cmocka_run_group_tests(tests, NULL, NULL);