From af187d1bc27a7390b358d7ad3b9f8f44cabe41ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Thu, 20 Nov 2025 12:46:49 +0100
Subject: [PATCH 7/7] don't segfault when listing module's features
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires

This is especially relevant when working with the "printed context" from
sysrepo, because that context has all the parsed info removed. As a
result, it is not possible to query a module's list of features. Let's
stop dereferencing a null pointer in that case.

Bug: https://github.com/sysrepo/sysrepo/issues/3695
Change-Id: I6258f580b4e5aa02cae2d60260b84593aefcf587
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
 CMakeLists.txt                |  1 +
 include/libyang-cpp/Utils.hpp |  1 +
 src/Module.cpp                |  3 +++
 src/utils/exception.cpp       |  5 +++++
 tests/context.cpp             | 12 ++++++++++++
 5 files changed, 22 insertions(+)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index cbaf82a..b9da66b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -118,6 +118,7 @@ if(BUILD_TESTING)
     endfunction()
 
     libyang_cpp_test(context)
+    target_link_libraries(test_context PkgConfig::LIBYANG)
     libyang_cpp_test(data_node)
     target_link_libraries(test_data_node PkgConfig::LIBYANG)
     libyang_cpp_test(schema_node)
diff --git a/include/libyang-cpp/Utils.hpp b/include/libyang-cpp/Utils.hpp
index 934714d..428b265 100644
--- a/include/libyang-cpp/Utils.hpp
+++ b/include/libyang-cpp/Utils.hpp
@@ -35,6 +35,7 @@ public:
 class LIBYANG_CPP_EXPORT ParsedInfoUnavailable : public Error {
 public:
     explicit ParsedInfoUnavailable();
+    explicit ParsedInfoUnavailable(const std::string& what);
 };
 
 /**
diff --git a/src/Module.cpp b/src/Module.cpp
index d6d4023..8eb8fb4 100644
--- a/src/Module.cpp
+++ b/src/Module.cpp
@@ -155,6 +155,9 @@ void Module::setImplemented(const AllFeatures)
  */
 std::vector<Feature> Module::features() const
 {
+    if (!m_module->parsed) {
+        throw ParsedInfoUnavailable{"Module::features: lys_module::parsed is not available"};
+    }
     std::vector<Feature> res;
     for (const auto& feature : std::span(m_module->parsed->features, LY_ARRAY_COUNT(m_module->parsed->features))) {
         res.emplace_back(Feature{&feature, m_ctx});
diff --git a/src/utils/exception.cpp b/src/utils/exception.cpp
index ecd2b85..a8d908b 100644
--- a/src/utils/exception.cpp
+++ b/src/utils/exception.cpp
@@ -22,6 +22,11 @@ ParsedInfoUnavailable::ParsedInfoUnavailable()
 {
 }
 
+ParsedInfoUnavailable::ParsedInfoUnavailable(const std::string& what)
+    : Error(what)
+{
+}
+
 ErrorCode ErrorWithCode::code() const
 {
     return m_errCode;
diff --git a/tests/context.cpp b/tests/context.cpp
index 355860b..5fce0f4 100644
--- a/tests/context.cpp
+++ b/tests/context.cpp
@@ -10,6 +10,7 @@
 #include <fstream>
 #include <libyang-cpp/Context.hpp>
 #include <libyang-cpp/Utils.hpp>
+#include <libyang/context.h>
 #include "example_schema.hpp"
 #include "pretty_printers.hpp"
 #include "test_vars.hpp"
@@ -333,6 +334,17 @@ TEST_CASE("context")
         REQUIRE(enabledFeatures == expectedEnabledFeatures);
     }
 
+    DOCTEST_SUBCASE("printed context")
+    {
+        ctx->setSearchDir(TESTS_DIR / "yang");
+        auto mod = ctx->loadModule("mod1", std::nullopt, {"*"});
+        REQUIRE(mod.features().size() == 3);
+        ly_ctx_free_parsed(retrieveContext(*ctx));
+        REQUIRE_THROWS_WITH_AS(mod.features(),
+                               "Module::features: lys_module::parsed is not available",
+                               libyang::ParsedInfoUnavailable);
+    }
+
     DOCTEST_SUBCASE("Module::setImplemented")
     {
         ctx->setSearchDir(TESTS_DIR / "yang");
-- 
2.43.0

