Repository: eclipse-cyclonedds/cyclonedds (C)

Issue:
IDL compiler allow optional key when nested in struct
The IDL compiler allows a value to be an optional key when the optional annotation is nested inside a struct. However, this behavior is not consistent with the specification in 7.2.2.4.4.4.8 Key Members, which states that "Key members shall never be optional, and they shall always have their 'must understand' bit set." To resolve this discrepancy, the IDL compiler and type object validation code should block this behavior.

Here is an example data structure that can be used to validate the error.

```
struct Plate {
    string number;
    @optional string state;
};
struct Car {
    @key Plate licensePlate;
    string color;
};
```



Fix (patch):
diff --git a/src/core/ddsi/src/ddsi_typewrap.c b/src/core/ddsi/src/ddsi_typewrap.c
index e030ccae65..38645b8d68 100644
--- a/src/core/ddsi/src/ddsi_typewrap.c
+++ b/src/core/ddsi/src/ddsi_typewrap.c
@@ -853,7 +853,7 @@ static dds_return_t xt_valid_type_flags (struct ddsi_domaingv *gv, uint16_t flag
 #define M DDS_XTypes_IS_MUST_UNDERSTAND
 #define K DDS_XTypes_IS_KEY
 #define D DDS_XTypes_IS_DEFAULT
-static dds_return_t xt_valid_member_flags (struct ddsi_domaingv *gv, uint16_t flags, uint8_t member_flag_kind)
+static dds_return_t xt_valid_member_flags (struct ddsi_domaingv *gv, uint16_t flags, uint8_t member_flag_kind, bool in_key)
 {
   dds_return_t ret = DDS_RETCODE_OK;
 
@@ -870,6 +870,10 @@ static dds_return_t xt_valid_member_flags (struct ddsi_domaingv *gv, uint16_t fl
     case MEMBER_FLAG_STRUCT_MEMBER:
       if (flags & ~(T1|T2|O|M|K|X))
         ret = DDS_RETCODE_BAD_PARAMETER;
+      if (in_key && (flags & O))
+        ret = DDS_RETCODE_BAD_PARAMETER;
+      if ((flags & O) && (flags & K))
+        ret = DDS_RETCODE_BAD_PARAMETER;
       break;
     case MEMBER_FLAG_UNION_MEMBER:
       if (flags & ~(T1|T2|D|X))
@@ -879,6 +883,8 @@ static dds_return_t xt_valid_member_flags (struct ddsi_domaingv *gv, uint16_t fl
       // must-understand not in spec
       if (flags & ~(T1|T2|M|K))
         ret = DDS_RETCODE_BAD_PARAMETER;
+      if (in_key && (flags & O))
+        ret = DDS_RETCODE_BAD_PARAMETER;
       break;
     case MEMBER_FLAG_ENUM_LITERAL:
       if (flags & ~(D))
@@ -932,11 +938,11 @@ static dds_return_t xt_valid_array_bounds (struct ddsi_domaingv *gv, const struc
   return DDS_RETCODE_OK;
 }
 
-static dds_return_t xt_validate_impl (struct ddsi_domaingv *gv, const struct xt_type *t, bool validate_hash_type)
+static dds_return_t xt_validate_impl (struct ddsi_domaingv *gv, const struct xt_type *t, bool validate_hash_type, bool in_key)
 {
   dds_return_t ret;
 
-  if (!validate_hash_type && !xt_is_non_hash (t))
+  if (!in_key && !validate_hash_type && !xt_is_non_hash (t))
     return DDS_RETCODE_OK;
 
   if (ddsi_xt_is_unresolved (t))
@@ -951,11 +957,32 @@ static dds_return_t xt_validate_impl (struct ddsi_domaingv *gv, const struct xt_
           || (ret = xt_valid_struct_member_ids (gv, t))
           || (ret = xt_valid_type_flags (gv, t->_u.structure.flags, t->_d)))
         return ret;
+
+      bool has_key_members = false;
+      for (const struct xt_type *t1 = t; t1 && ddsi_xt_is_resolved (t1); t1 = t1->_u.structure.base_type ? &t1->_u.structure.base_type->xt : NULL)
+        for (uint32_t n = 0; !has_key_members && n < t1->_u.structure.members.length; n++)
+          has_key_members = (t1->_u.structure.members.seq[n].flags & DDS_XTypes_IS_KEY);
+
       for (uint32_t n = 0; n < t->_u.structure.members.length; n++)
       {
-        if ((ret = xt_valid_member_flags (gv, t->_u.structure.members.seq[n].flags, MEMBER_FLAG_STRUCT_MEMBER)))
+        DDS_XTypes_StructMemberFlag flags = t->_u.structure.members.seq[n].flags;
+        /* A member is considered a key-member (and therefore cannot be optional)
+           in case (1) the member has a key flag or (2) no member of the struct
+           and it's base types has a key flag and the 'parent' member (the one
+           that has this struct as it's member type) is a key (by either rule 1
+           or 2). As a result, a type can be valid or invalid based on the context
+           it is used in:
+              struct A { @optional long x; };
+              struct B { @key A y; };
+              struct C { B z; };
+           Type B is an invalid top-level type for a topic. Type C is a valid
+           top-level type, but will still be rejected because of the key flag
+           in B.
+        */
+        bool key = (in_key && !has_key_members) || (flags & DDS_XTypes_IS_KEY);
+        if ((ret = xt_valid_member_flags (gv, flags, MEMBER_FLAG_STRUCT_MEMBER, key)))
           return ret;
-        if ((ret = xt_validate_impl (gv, &t->_u.structure.members.seq[n].type->xt, false)))
+        if ((ret = xt_validate_impl (gv, &t->_u.structure.members.seq[n].type->xt, false, key)))
           return ret;
       }
       break;
@@ -963,15 +990,15 @@ static dds_return_t xt_validate_impl (struct ddsi_domaingv *gv, const struct xt_
       if (((ret = xt_valid_union_disc_type (gv, t)))
           || (ret = xt_valid_union_member_ids (gv, t))
           || (ret = xt_valid_type_flags (gv, t->_u.union_type.flags, t->_d))
-          || (ret = xt_valid_member_flags (gv, t->_u.union_type.disc_flags, MEMBER_FLAG_UNION_DISC)))
+          || (ret = xt_valid_member_flags (gv, t->_u.union_type.disc_flags, MEMBER_FLAG_UNION_DISC, in_key)))
         return ret;
       bool has_default = false;
       for (uint32_t n = 0; n < t->_u.union_type.members.length; n++)
       {
         DDS_XTypes_UnionMemberFlag flags = t->_u.union_type.members.seq[n].flags;
-        if ((ret = xt_valid_member_flags (gv, flags, MEMBER_FLAG_UNION_MEMBER)))
+        if ((ret = xt_valid_member_flags (gv, flags, MEMBER_FLAG_UNION_MEMBER, in_key)))
           return ret;
-        if ((ret = xt_validate_impl (gv, &t->_u.union_type.members.seq[n].type->xt, false)))
+        if ((ret = xt_validate_impl (gv, &t->_u.union_type.members.seq[n].type->xt, false, in_key)))
           return ret;
         if (flags & DDS_XTypes_IS_DEFAULT)
         {
@@ -992,7 +1019,7 @@ static dds_return_t xt_validate_impl (struct ddsi_domaingv *gv, const struct xt_
       if (t->_u.enum_type.bit_bound > 32)
         return DDS_RETCODE_BAD_PARAMETER;
       for (uint32_t n = 0; n < t->_u.enum_type.literals.length; n++)
-        if ((ret = xt_valid_member_flags (gv, t->_u.enum_type.literals.seq[n].flags, MEMBER_FLAG_ENUM_LITERAL)))
+        if ((ret = xt_valid_member_flags (gv, t->_u.enum_type.literals.seq[n].flags, MEMBER_FLAG_ENUM_LITERAL, in_key)))
           return ret;
       break;
     case DDS_XTypes_TK_BITMASK:
@@ -1002,13 +1029,13 @@ static dds_return_t xt_validate_impl (struct ddsi_domaingv *gv, const struct xt_
       if (t->_u.bitmask.bit_bound > 64)
         return DDS_RETCODE_BAD_PARAMETER;
       for (uint32_t n = 0; n < t->_u.bitmask.bitflags.length; n++)
-        if ((ret = xt_valid_member_flags (gv, t->_u.bitmask.bitflags.seq[n].flags, MEMBER_FLAG_BIT_FLAG)))
+        if ((ret = xt_valid_member_flags (gv, t->_u.bitmask.bitflags.seq[n].flags, MEMBER_FLAG_BIT_FLAG, in_key)))
           return ret;
       break;
     case DDS_XTypes_TK_ALIAS:
       if ((ret = xt_valid_type_flags (gv, t->_u.alias.flags, t->_d))
-          || (ret = xt_valid_member_flags (gv, t->_u.alias.related_flags, MEMBER_FLAG_ALIAS_MEMBER))
-          || (ret = xt_validate_impl (gv, &t->_u.alias.related_type->xt, false)))
+          || (ret = xt_valid_member_flags (gv, t->_u.alias.related_flags, MEMBER_FLAG_ALIAS_MEMBER, in_key))
+          || (ret = xt_validate_impl (gv, &t->_u.alias.related_type->xt, false, in_key)))
         return ret;
       break;
     case DDS_XTypes_TK_BITSET:
@@ -1021,22 +1048,22 @@ static dds_return_t xt_validate_impl (struct ddsi_domaingv *gv, const struct xt_
       break;
     case DDS_XTypes_TK_SEQUENCE:
       if ((ret = xt_valid_type_flags (gv, t->_u.seq.c.flags, t->_d))
-          || (ret = xt_valid_member_flags (gv, t->_u.seq.c.element_flags, MEMBER_FLAG_COLLECTION_ELEMENT))
-          || (ret = xt_validate_impl (gv, &t->_u.seq.c.element_type->xt, false)))
+          || (ret = xt_valid_member_flags (gv, t->_u.seq.c.element_flags, MEMBER_FLAG_COLLECTION_ELEMENT, in_key))
+          || (ret = xt_validate_impl (gv, &t->_u.seq.c.element_type->xt, false, in_key)))
         return ret;
       break;
     case DDS_XTypes_TK_ARRAY:
       if ((ret = xt_valid_type_flags (gv, t->_u.array.c.flags, t->_d))
-          || (ret = xt_valid_member_flags (gv, t->_u.array.c.element_flags, MEMBER_FLAG_COLLECTION_ELEMENT))
-          || (ret = xt_validate_impl (gv, &t->_u.array.c.element_type->xt, false))
+          || (ret = xt_valid_member_flags (gv, t->_u.array.c.element_flags, MEMBER_FLAG_COLLECTION_ELEMENT, in_key))
+          || (ret = xt_validate_impl (gv, &t->_u.array.c.element_type->xt, false, in_key))
           || (ret = xt_valid_array_bounds (gv, t)))
         return ret;
       break;
     case DDS_XTypes_TK_MAP:
       if ((ret = xt_valid_type_flags (gv, t->_u.map.c.flags, t->_d))
-          || (ret = xt_valid_member_flags (gv, t->_u.map.c.element_flags, MEMBER_FLAG_COLLECTION_ELEMENT))
-          || (ret = xt_validate_impl (gv, &t->_u.map.key_type->xt, false))
-          || (ret = xt_validate_impl (gv, &t->_u.map.c.element_type->xt, false)))
+          || (ret = xt_valid_member_flags (gv, t->_u.map.c.element_flags, MEMBER_FLAG_COLLECTION_ELEMENT, in_key))
+          || (ret = xt_validate_impl (gv, &t->_u.map.key_type->xt, false, in_key))
+          || (ret = xt_validate_impl (gv, &t->_u.map.c.element_type->xt, false, in_key)))
         return ret;
       break;
     case DDS_XTypes_TK_BOOLEAN: case DDS_XTypes_TK_BYTE:
@@ -1054,7 +1081,7 @@ static dds_return_t xt_validate_impl (struct ddsi_domaingv *gv, const struct xt_
 
 dds_return_t ddsi_xt_validate (struct ddsi_domaingv *gv, const struct xt_type *t)
 {
-  return xt_validate_impl (gv, t, true);
+  return xt_validate_impl (gv, t, true, false);
 }
 
 static dds_return_t add_minimal_typeobj (struct ddsi_domaingv *gv, struct xt_type *xt, const struct DDS_XTypes_TypeObject *to)
diff --git a/src/idl/src/tree.c b/src/idl/src/tree.c
index 8fe4b94813..24d39028ea 100644
--- a/src/idl/src/tree.c
+++ b/src/idl/src/tree.c
@@ -3700,7 +3700,14 @@ static bool no_specific_key(const void *node)
 {
   /* @key(FALSE) is equivalent to missing @key(?) */
   if (idl_mask(node) & IDL_STRUCT) {
-    const idl_member_t *member = ((const idl_struct_t *)node)->members;
+    const idl_struct_t *_struct = (const idl_struct_t *)node;
+    if (_struct->inherit_spec)
+    {
+      if (!no_specific_key(_struct->inherit_spec->base))
+        return false;
+    }
+
+    const idl_member_t *member = _struct->members;
     for (; member; member = idl_next(member)) {
       if (member->key.value)
         return false;
diff --git a/src/tools/idlc/src/libidlc/libidlc__descriptor.c b/src/tools/idlc/src/libidlc/libidlc__descriptor.c
index ed53946d42..c23d3e25d1 100644
--- a/src/tools/idlc/src/libidlc/libidlc__descriptor.c
+++ b/src/tools/idlc/src/libidlc/libidlc__descriptor.c
@@ -2119,6 +2119,11 @@ static idl_retcode_t get_ctype_keys(const idl_pstate_t *pstate, struct descripto
           if (parent_is_key && !ctype->has_key_member)
             inst->data.opcode.code |= DDS_OP_FLAG_KEY;
           if (inst->data.opcode.code & DDS_OP_FLAG_KEY) {
+            if (inst->data.opcode.code & DDS_OP_FLAG_OPT) {
+              idl_error (pstate, ctype->node, "A member that is part of a key (possibly nested inside a struct) cannot be optional");
+              ret = IDL_RETCODE_SYNTAX_ERROR;
+              goto err;
+            }
             if ((ret = get_ctype_keys_adr(pstate, descriptor, offs, base_type_ops_offs, inst, ctype, n_keys, &ctype_keys)) != IDL_RETCODE_OK)
               goto err;
           }