summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/model/Domain.cpp37
-rw-r--r--src/core/model/Domain.hpp1
-rw-r--r--test/core/model/DomainTest.cpp185
3 files changed, 213 insertions, 10 deletions
diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp
index ca8f889..8255401 100644
--- a/src/core/model/Domain.cpp
+++ b/src/core/model/Domain.cpp
@@ -324,21 +324,31 @@ bool FieldDescriptor::doValidate(Logger &logger) const
return valid;
}
-static void gatherSubclasses(NodeVector<StructuredClass> &res,
- Handle<StructuredClass> strct)
+static void gatherSubclasses(
+ std::unordered_set<const StructuredClass *>& visited,
+ NodeVector<StructuredClass> &res, Handle<StructuredClass> strct)
{
+ // this check is to prevent cycles.
+ if (!visited.insert(strct.get()).second) {
+ return;
+ }
for (auto sub : strct->getSubclasses()) {
+ // this check is to prevent cycles.
+ if(visited.count(sub.get())){
+ continue;
+ }
res.push_back(sub);
- gatherSubclasses(res, sub);
+ gatherSubclasses(visited, res, sub);
}
}
NodeVector<StructuredClass> FieldDescriptor::getChildrenWithSubclasses() const
{
+ std::unordered_set<const StructuredClass *> visited;
NodeVector<StructuredClass> res;
for (auto c : children) {
res.push_back(c);
- gatherSubclasses(res, c);
+ gatherSubclasses(visited, res, c);
}
return res;
}
@@ -566,8 +576,9 @@ bool Descriptor::addAndSortFieldDescriptor(Handle<FieldDescriptor> fd,
if (fds.find(fd) == fds.end()) {
invalidate();
// check if the previous field is a tree field already.
- if (!fds.empty() && !fieldDescriptors.empty() &&
- fds.back()->getFieldType() == FieldDescriptor::FieldType::TREE &&
+ if (!fieldDescriptors.empty() &&
+ fieldDescriptors.back()->getFieldType() ==
+ FieldDescriptor::FieldType::TREE &&
fd->getFieldType() != FieldDescriptor::FieldType::TREE) {
// if so we add the new field before the TREE field.
fieldDescriptors.insert(fieldDescriptors.end() - 1, fd);
@@ -776,8 +787,13 @@ void StructuredClass::removeSubclass(Handle<StructuredClass> sc, Logger &logger)
Rooted<FieldDescriptor> StructuredClass::gatherFieldDescriptors(
NodeVector<FieldDescriptor> &current,
+ std::unordered_set<const StructuredClass *> &visited,
std::set<std::string> &overriddenFields, bool hasTREE) const
{
+ // this check is to prevent cycles of inheritance to mess up this function.
+ if (!visited.insert(this).second) {
+ return nullptr;
+ }
Rooted<FieldDescriptor> mainField;
NodeVector<FieldDescriptor> tmp;
// first gather the non-overridden fields.
@@ -798,8 +814,8 @@ Rooted<FieldDescriptor> StructuredClass::gatherFieldDescriptors(
if (superclass != nullptr) {
Rooted<FieldDescriptor> super_main_field =
- superclass->gatherFieldDescriptors(current, overriddenFields,
- hasTREE);
+ superclass->gatherFieldDescriptors(current, visited,
+ overriddenFields, hasTREE);
if (!hasTREE) {
mainField = super_main_field;
}
@@ -814,9 +830,10 @@ NodeVector<FieldDescriptor> StructuredClass::getFieldDescriptors() const
{
// in this case we return a NodeVector of Rooted entries without owner.
NodeVector<FieldDescriptor> vec;
+ std::unordered_set<const StructuredClass *> visited;
std::set<std::string> overriddenFields;
Rooted<FieldDescriptor> mainField =
- gatherFieldDescriptors(vec, overriddenFields, false);
+ gatherFieldDescriptors(vec, visited, overriddenFields, false);
if (mainField != nullptr) {
vec.push_back(mainField);
}
@@ -961,4 +978,4 @@ const Rtti Domain = RttiBuilder<ousia::Domain>("Domain")
.parent(&RootNode)
.composedOf({&StructuredClass, &AnnotationClass});
}
-}
+} \ No newline at end of file
diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp
index 476a38c..b3fc6c2 100644
--- a/src/core/model/Domain.hpp
+++ b/src/core/model/Domain.hpp
@@ -808,6 +808,7 @@ private:
*/
Rooted<FieldDescriptor> gatherFieldDescriptors(
NodeVector<FieldDescriptor> &current,
+ std::unordered_set<const StructuredClass *> &visited,
std::set<std::string> &overriddenFields, bool hasTREE) const;
protected:
diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp
index d68648e..6bbf26d 100644
--- a/test/core/model/DomainTest.cpp
+++ b/test/core/model/DomainTest.cpp
@@ -81,6 +81,115 @@ TEST(Domain, testDomainResolving)
assert_path(res[0], &RttiTypes::StructuredClass, {"book", "paragraph"});
}
+// i use this wrapper due to the strange behaviour of GTEST.
+static void assertFalse(bool b){
+ ASSERT_FALSE(b);
+}
+
+static Rooted<FieldDescriptor> createUnsortedPrimitiveField(
+ Handle<StructuredClass> strct, Handle<Type> type, Logger &logger, bool tree,
+ std::string name)
+{
+ FieldDescriptor::FieldType fieldType = FieldDescriptor::FieldType::SUBTREE;
+ if (tree) {
+ fieldType = FieldDescriptor::FieldType::TREE;
+ }
+
+ auto res = strct->createPrimitiveFieldDescriptor(type, logger, fieldType,
+ std::move(name));
+ assertFalse(res.second);
+ return res.first;
+}
+
+TEST(StructuredClass, getFieldDescriptors)
+{
+ /*
+ * We construct a case with the three levels:
+ * 1.) A has the SUBTREE fields a and b as well as a TREE field.
+ * 2.) B is a subclass of A and has the SUBTREE fields b and c as well as
+ * a TREE field.
+ * 3.) C is a subclass of B and has the SUBTREE field a.
+ * As a result we expect C to have none of As fields, the TREE field of B,
+ * and the SUBTREE fields a (of C) , b and c (of B).
+ */
+ TerminalLogger logger{std::cout};
+ Manager mgr{1};
+ Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)};
+ Rooted<Domain> domain{new Domain(mgr, sys, "myDomain")};
+
+ Rooted<StructuredClass> A{new StructuredClass(
+ mgr, "A", domain, Cardinality::any(), nullptr, false, true)};
+ Rooted<FieldDescriptor> A_a = createUnsortedPrimitiveField(
+ A, sys->getStringType(), logger, false, "a");
+ Rooted<FieldDescriptor> A_b = createUnsortedPrimitiveField(
+ A, sys->getStringType(), logger, false, "b");
+ Rooted<FieldDescriptor> A_main = createUnsortedPrimitiveField(
+ A, sys->getStringType(), logger, true, "somename");
+
+ Rooted<StructuredClass> B{new StructuredClass(
+ mgr, "B", domain, Cardinality::any(), A, false, true)};
+ Rooted<FieldDescriptor> B_b = createUnsortedPrimitiveField(
+ B, sys->getStringType(), logger, false, "b");
+ Rooted<FieldDescriptor> B_c = createUnsortedPrimitiveField(
+ B, sys->getStringType(), logger, false, "c");
+ Rooted<FieldDescriptor> B_main = createUnsortedPrimitiveField(
+ B, sys->getStringType(), logger, true, "othername");
+
+ Rooted<StructuredClass> C{new StructuredClass(
+ mgr, "C", domain, Cardinality::any(), B, false, true)};
+ Rooted<FieldDescriptor> C_a = createUnsortedPrimitiveField(
+ C, sys->getStringType(), logger, false, "a");
+
+ ASSERT_TRUE(domain->validate(logger));
+
+ // check all FieldDescriptors
+ {
+ NodeVector<FieldDescriptor> fds = A->getFieldDescriptors();
+ ASSERT_EQ(3, fds.size());
+ ASSERT_EQ(A_a, fds[0]);
+ ASSERT_EQ(A_b, fds[1]);
+ ASSERT_EQ(A_main, fds[2]);
+ }
+ {
+ NodeVector<FieldDescriptor> fds = B->getFieldDescriptors();
+ ASSERT_EQ(4, fds.size());
+ ASSERT_EQ(A_a, fds[0]);
+ ASSERT_EQ(B_b, fds[1]);
+ ASSERT_EQ(B_c, fds[2]);
+ ASSERT_EQ(B_main, fds[3]);
+ }
+ {
+ NodeVector<FieldDescriptor> fds = C->getFieldDescriptors();
+ ASSERT_EQ(4, fds.size());
+ ASSERT_EQ(B_b, fds[0]);
+ ASSERT_EQ(B_c, fds[1]);
+ // superclass fields come before subclass fields (except for the TREE
+ // field, which is always last).
+ ASSERT_EQ(C_a, fds[2]);
+ ASSERT_EQ(B_main, fds[3]);
+ }
+}
+
+
+TEST(StructuredClass, getFieldDescriptorsCycles)
+{
+ Logger logger;
+ Manager mgr{1};
+ Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)};
+ Rooted<Domain> domain{new Domain(mgr, sys, "myDomain")};
+
+ Rooted<StructuredClass> A{new StructuredClass(
+ mgr, "A", domain, Cardinality::any(), nullptr, false, true)};
+ A->addSubclass(A, logger);
+ Rooted<FieldDescriptor> A_a = createUnsortedPrimitiveField(
+ A, sys->getStringType(), logger, false, "a");
+ ASSERT_FALSE(domain->validate(logger));
+ // if we call getFieldDescriptors that should still return a valid result.
+ NodeVector<FieldDescriptor> fds = A->getFieldDescriptors();
+ ASSERT_EQ(1, fds.size());
+ ASSERT_EQ(A_a, fds[0]);
+}
+
Rooted<StructuredClass> getClass(const std::string name, Handle<Domain> dom)
{
std::vector<ResolutionResult> res =
@@ -221,6 +330,34 @@ TEST(Descriptor, pathToAdvanced)
ASSERT_EQ("", path[2]->getName());
}
+TEST(Descriptor, pathToCycles)
+{
+ // build a domain with a cycle.
+ Manager mgr{1};
+ Logger logger;
+ Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)};
+ // Construct the domain
+ Rooted<Domain> domain{new Domain(mgr, sys, "cycles")};
+ Rooted<StructuredClass> A{new StructuredClass(
+ mgr, "A", domain, Cardinality::any(), {nullptr}, true, true)};
+ A->addSubclass(A, logger);
+ ASSERT_FALSE(domain->validate(logger));
+ Rooted<StructuredClass> B{new StructuredClass(
+ mgr, "B", domain, Cardinality::any(), {nullptr}, false, true)};
+ Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger).first;
+ A_field->addChild(B);
+ /*
+ * Now try to create the path from A to B. A direct path is possible but
+ * in the worst case this could also try to find shorter paths via an
+ * endless repition of A instances.
+ * As we cut the search tree at paths that are longer than our current
+ * optimum this should not happen, though.
+ */
+ NodeVector<Node> path = A->pathTo(B, logger);
+ ASSERT_EQ(1, path.size());
+ ASSERT_EQ(A_field, path[0]);
+}
+
TEST(Descriptor, getDefaultFields)
{
// construct a domain with lots of default fields to test.
@@ -301,6 +438,29 @@ TEST(Descriptor, getDefaultFields)
ASSERT_EQ(F_field, fields[1]);
}
+TEST(Descriptor, getDefaultFieldsCycles)
+{
+ // build a domain with a cycle.
+ Manager mgr{1};
+ Logger logger;
+ Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)};
+ // Construct the domain
+ Rooted<Domain> domain{new Domain(mgr, sys, "cycles")};
+ Rooted<StructuredClass> A{new StructuredClass(
+ mgr, "A", domain, Cardinality::any(), {nullptr}, true, true)};
+ A->addSubclass(A, logger);
+ ASSERT_FALSE(domain->validate(logger));
+ Rooted<FieldDescriptor> A_field =
+ A->createPrimitiveFieldDescriptor(sys->getStringType(), logger).first;
+ /*
+ * Now try to get the default fields of A. This should not lead to cycles
+ * if we correctly note all already visited nodes.
+ */
+ NodeVector<FieldDescriptor> defaultFields = A->getDefaultFields();
+ ASSERT_EQ(1, defaultFields.size());
+ ASSERT_EQ(A_field, defaultFields[0]);
+}
+
TEST(Descriptor, getPermittedChildren)
{
// analyze the book domain.
@@ -338,6 +498,31 @@ TEST(Descriptor, getPermittedChildren)
ASSERT_EQ(sub, children[3]);
}
+TEST(Descriptor, getPermittedChildrenCycles)
+{
+ // build a domain with a cycle.
+ Manager mgr{1};
+ Logger logger;
+ Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)};
+ // Construct the domain
+ Rooted<Domain> domain{new Domain(mgr, sys, "cycles")};
+ Rooted<StructuredClass> A{new StructuredClass(
+ mgr, "A", domain, Cardinality::any(), {nullptr}, true, true)};
+ A->addSubclass(A, logger);
+ ASSERT_FALSE(domain->validate(logger));
+ Rooted<FieldDescriptor> A_field = A->createFieldDescriptor(logger).first;
+ // we make the cycle worse by adding A as child of itself.
+ A_field->addChild(A);
+ /*
+ * Now try to get the permitted children of A. This should not lead to
+ * cycles
+ * if we correctly note all already visited nodes.
+ */
+ NodeVector<StructuredClass> children = A->getPermittedChildren();
+ ASSERT_EQ(1, children.size());
+ ASSERT_EQ(A, children[0]);
+}
+
TEST(StructuredClass, isSubclassOf)
{
// create an inheritance hierarchy.