schema compile CHANGE check for circular reference of features
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index 72c8e70..9c95a9f 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -723,6 +723,56 @@
}
/**
+ * @brief Check circular dependency of features - feature MUST NOT reference itself (via their if-feature statement).
+ * @param[in] ctx Compile context for logging.
+ * @param[in] feature The feature referenced in if-feature statement (its depfeatures list is being extended)
+ * @param[in] depfeatures The list of depending features of a feature being currently processed (not the one provided as @p feature)
+ * @return LY_SUCCESS if everything is ok.
+ * @return LY_EVALID if the feature references indirectly itself.
+ */
+static LY_ERR
+lys_compile_feature_circular_check(struct lysc_ctx *ctx, struct lysc_feature *feature, struct lysc_feature **depfeatures)
+{
+ LY_ERR ret = LY_EVALID;
+ unsigned int u, v;
+ struct ly_set recursion = {0};
+ struct lysc_feature *drv;
+
+ if (!depfeatures) {
+ return LY_SUCCESS;
+ }
+
+ for (u = 0; u < LY_ARRAY_SIZE(depfeatures); ++u) {
+ if (feature == depfeatures[u]) {
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
+ "Feature \"%s\" is indirectly referenced from itself.", feature->name);
+ goto cleanup;
+ }
+ ly_set_add(&recursion, depfeatures[u], 0);
+ }
+
+ for (v = 0; v < recursion.count; ++v) {
+ drv = recursion.objs[v];
+ if (!drv->depfeatures) {
+ continue;
+ }
+ for (u = 0; u < LY_ARRAY_SIZE(drv->depfeatures); ++u) {
+ if (feature == drv->depfeatures[u]) {
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
+ "Feature \"%s\" is indirectly referenced from itself.", feature->name);
+ goto cleanup;
+ }
+ ly_set_add(&recursion, drv->depfeatures[u], 0);
+ }
+ }
+ ret = LY_SUCCESS;
+
+cleanup:
+ ly_set_erase(&recursion, NULL);
+ return ret;
+}
+
+/**
* @brief Create pre-compiled features array.
*
* See lys_feature_precompile() for more details.
@@ -754,11 +804,19 @@
for (u = 0; u < LY_ARRAY_SIZE(feature->iffeatures); ++u) {
if (feature->iffeatures[u].features) {
for (v = 0; v < LY_ARRAY_SIZE(feature->iffeatures[u].features); ++v) {
+ /* check for circular dependency - direct reference first,... */
+ if (feature == feature->iffeatures[u].features[v]) {
+ LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
+ "Feature \"%s\" is referenced from itself.", feature->name);
+ return LY_EVALID;
+ }
+ /* ... and indirect circular reference */
+ LY_CHECK_RET(lys_compile_feature_circular_check(ctx, feature->iffeatures[u].features[v], feature->depfeatures));
+
/* add itself into the dependants list */
LY_ARRAY_NEW_RET(ctx->ctx, feature->iffeatures[u].features[v]->depfeatures, df, LY_EMEM);
*df = feature;
}
- /* TODO check for circular dependency */
}
}
}
diff --git a/tests/src/test_tree_schema_compile.c b/tests/src/test_tree_schema_compile.c
index 3894f70..0591479 100644
--- a/tests/src/test_tree_schema_compile.c
+++ b/tests/src/test_tree_schema_compile.c
@@ -354,6 +354,11 @@
assert_null(lys_parse_mem(ctx.ctx, "module z{namespace urn:z; prefix z; include sz;feature f1;}", LYS_IN_YANG));
logbuf_assert("Duplicate identifier \"f1\" of feature statement.");
+ assert_null(lys_parse_mem(ctx.ctx, "module aa{namespace urn:aa; prefix aa; feature f1 {if-feature f2;} feature f2 {if-feature f1;}}", LYS_IN_YANG));
+ logbuf_assert("Feature \"f1\" is indirectly referenced from itself.");
+ assert_null(lys_parse_mem(ctx.ctx, "module ab{namespace urn:ab; prefix ab; feature f1 {if-feature f1;}}", LYS_IN_YANG));
+ logbuf_assert("Feature \"f1\" is referenced from itself.");
+
/* import reference */
assert_non_null(modp = lys_parse_mem(ctx.ctx, str, LYS_IN_YANG));
assert_int_equal(LY_SUCCESS, lys_feature_enable(modp, "f1"));