Default XTypes Minimal TypeMap collapses distinct typedef aliases with identical underlying sequence type
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 1.5k
- Forks
- 516
- Avg merge
- 4d 4h
- Merged PRs (30d)
- 4
Description
Default XTypes Minimal TypeMap collapses distinct typedef aliases with identical underlying sequence type
Summary
OpenDDS default XTypes metadata generation collapses two distinct IDL typedef
aliases when both aliases have the same underlying sequence type.
In the default generated Minimal TypeMap, the topic type below exposes only one
TK_ALIAS entry even though the IDL contains two different typedef names,
ScoreSeq and KeyAliasSeq, and both names are used by topic members.
When the same IDL is generated with opendds_idl -Gxtypes-complete, OpenDDS
does preserve both aliases in the Complete TypeMap. Therefore this is not a CDR
serialization/deserialization bug and not a Complete TypeObject loss bug. The
problem is specifically in the default Minimal XTypes metadata path: alias names
that may carry schema-level semantics are not discoverable unless Complete
TypeObjects are explicitly generated.
This affects dynamic type discovery, schema introspection, type debugging, and
interoperability tools that distinguish aliases as semantic roles rather than
only as structurally equivalent wire types.
Trigger
The IDL was:
module test {
typedef sequence<long, 3> KeyAliasSeq;
typedef sequence<long, 3> ScoreSeq;
struct S5_B2_StringBound_0000 {
string<5> name;
sequence<long, 5> values;
ScoreSeq score_alias_probe;
KeyAliasSeq key_alias_sequence;
};
};
All three DDS implementations accepted and decoded the value-level payload
correctly. The mismatch was in type metadata:
OpenDDS:
minimal_type_map_size = 2
Fast DDS:
typeobject_registration_count = 3
typeobject_registration_names = [
test::KeyAliasSeq,
test::S5_B2_StringBound_0000,
test::ScoreSeq
]
CycloneDDS:
generated topic descriptor and type mapping successfully
Minimal Reproducer
I minimized the fuzz input to this standalone IDL:
module test {
typedef sequence<long, 3> KeyAliasSeq;
typedef sequence<long, 3> ScoreSeq;
struct AliasTypeMapProbe {
string<5> name;
sequence<long, 5> values;
ScoreSeq score_alias_probe;
KeyAliasSeq key_alias_sequence;
};
};
The only important property is that KeyAliasSeq and ScoreSeq are two
distinct typedef declarations with the same related/base type:
typedef sequence<long, 3> KeyAliasSeq;
typedef sequence<long, 3> ScoreSeq;
Local Reproduction Steps
The attached reproducer contains:
pocs/opendds_typedef_alias_typemap/
test.idl
typeinfo_probe.cpp
reproduce.sh
Run it from the repository root:
cd pocs/opendds_typedef_alias_typemap
./reproduce.sh
To test another OpenDDS installation:
OPENDDS=/path/to/opendds ./reproduce.sh
The script performs two builds:
- default
opendds_idlgeneration; opendds_idl -Gxtypes-completegeneration.
Then it compiles and runs typeinfo_probe.cpp, which calls:
OpenDDS::DCPS::getMinimalTypeIdentifier<TopicTag>();
OpenDDS::DCPS::getMinimalTypeMap<TopicTag>();
OpenDDS::DCPS::getCompleteTypeIdentifier<TopicTag>(); // only with -Gxtypes-complete
OpenDDS::DCPS::getCompleteTypeMap<TopicTag>(); // only with -Gxtypes-complete
The probe prints the TypeMap size, TypeObject kind, and Complete alias names
when Complete TypeObjects are available.
Environment
Observed with:
TAO_IDL_FE, version 2.5.24
OpenDDS version 3.34.0
OpenDDS source revision: 1a9f3ccca65c8933f2698a7cd45a1cef54c64780
Actual Result
Default OpenDDS generation:
[1/4] Generate default Minimal TypeObject build
[2/4] Compile and run Minimal probe
minimal_topic_ti_kind=241
minimal_topic_ti_hash=113, 113, 35, 247, 146, 25, 173, 106, 108, 151, 36, 162, 167, 72
minimal_type_map_size=2
minimal entry ... object_kind=TK_STRUCTURE
minimal entry ... object_kind=TK_ALIAS
minimal_alias_count=1
minimal_sequence_count=0
minimal_struct_count=1
Even though the IDL has two aliases, only one Minimal TK_ALIAS entry is
visible.
Generation with -Gxtypes-complete:
[3/4] Generate build with -Gxtypes-complete
[4/4] Compile and run Complete probe
minimal_type_map_size=2
minimal_alias_count=1
complete_topic_ti_kind=242
complete_type_map_size=3
complete entry ... object_kind=TK_ALIAS alias_name=test::ScoreSeq
complete entry ... object_kind=TK_ALIAS alias_name=test::KeyAliasSeq
complete entry ... object_kind=TK_STRUCTURE struct_name=test::AliasTypeMapProbe
complete_alias_count=2
complete_sequence_count=0
complete_struct_count=1
This confirms that OpenDDS has enough information to preserve both alias names
in Complete TypeObjects, but the default Minimal metadata path exposes only one
canonicalized alias.
Generated C++ Evidence
In the default generated file:
pocs/opendds_typedef_alias_typemap/build/minimal/testTypeSupportImpl.cpp
both typedef tags get the same Minimal TypeIdentifier:
KeyAliasSeq: lines 34-40
ScoreSeq: lines 278-284
// KeyAliasSeq
ti = XTypes::TypeIdentifier(
XTypes::EK_MINIMAL,
XTypes::EquivalenceHashWrapper(255, 250, 172, 151, 160, 229, 143, 132, 85, 57, 98, 7, 100, 81));
// ScoreSeq
ti = XTypes::TypeIdentifier(
XTypes::EK_MINIMAL,
XTypes::EquivalenceHashWrapper(255, 250, 172, 151, 160, 229, 143, 132, 85, 57, 98, 7, 100, 81));
The default generated Minimal TypeMap contains only two entries: one structure
and one alias:
build/minimal/testTypeSupportImpl.cpp:1607-1612
XTypes::TypeMap get_minimal_type_map_private()
{
XTypes::TypeMap tm;
tm[<minimal struct TypeIdentifier>] = minimal_to_0(); // AliasTypeMapProbe
tm[<minimal alias TypeIdentifier>] = minimal_to_1(); // one collapsed alias
return tm;
}
With -Gxtypes-complete, the generated Complete TypeMap contains both aliases
as separate entries:
build/complete/testTypeSupportImpl.cpp:1691-1697
XTypes::TypeMap get_complete_type_map_private()
{
XTypes::TypeMap tm;
tm[<complete alias TypeIdentifier>] = complete_to_0(); // test::ScoreSeq
tm[<complete alias TypeIdentifier>] = complete_to_1(); // test::KeyAliasSeq
tm[<complete struct TypeIdentifier>] = complete_to_2(); // test::AliasTypeMapProbe
return tm;
}
Code-Level Root Cause Analysis
The behavior appears to come from the OpenDDS TypeObject generator.
1. Minimal alias TypeObject does not include the alias name
In dds/idl/typeobject_generator.cpp:1606-1624,
generate_alias_type_identifier() builds both Minimal and Complete alias
TypeObjects.
Relevant code:
// dds/idl/typeobject_generator.cpp
void typeobject_generator::generate_alias_type_identifier(AST_Type* type)
{
AST_Typedef* const n = ast_cast<AST_Typedef>(type);
OpenDDS::XTypes::TypeObject minimal_to, complete_to;
minimal_to.kind = OpenDDS::XTypes::EK_MINIMAL;
minimal_to.minimal.kind = OpenDDS::XTypes::TK_ALIAS;
minimal_to.minimal.alias_type.alias_flags = 0;
minimal_to.minimal.alias_type.body.common.related_flags = 0;
minimal_to.minimal.alias_type.body.common.related_type =
get_minimal_type_identifier(n->base_type());
complete_to.kind = OpenDDS::XTypes::EK_COMPLETE;
complete_to.complete.kind = OpenDDS::XTypes::TK_ALIAS;
complete_to.complete.alias_type.alias_flags = 0;
complete_to.complete.alias_type.header.detail.type_name =
canonical_name(type->name());
complete_to.complete.alias_type.body.common.related_flags = 0;
complete_to.complete.alias_type.body.common.related_type =
get_complete_type_identifier(n->base_type());
update_maps(type, minimal_to, complete_to);
}
The Minimal alias only contains related_type. It does not contain
canonical_name(type->name()). Therefore two typedef aliases with the same
base type generate byte-identical Minimal alias TypeObjects.
The Complete alias includes:
complete_to.complete.alias_type.header.detail.type_name =
canonical_name(type->name());
so Complete TypeObjects correctly differ for ScoreSeq and KeyAliasSeq.
2. TypeMap insertion is keyed by the generated TypeIdentifier
dds/idl/typeobject_generator.cpp:1050-1068, update_maps() computes the
Minimal TypeIdentifier from the Minimal TypeObject and inserts it into
minimal_type_map_:
// dds/idl/typeobject_generator.cpp
void typeobject_generator::update_maps(
AST_Type* type,
const OpenDDS::XTypes::TypeObject& minimal_to,
const OpenDDS::XTypes::TypeObject& complete_to)
{
const TypeObjectPair to_pair = {minimal_to, complete_to};
type_object_map_[type] = to_pair;
if (hash_type_identifier_map_.count(type) == 0) {
const OpenDDS::XTypes::TypeIdentifier minimal_ti =
makeTypeIdentifier(minimal_to, typeid_encoding_);
const OpenDDS::XTypes::TypeIdentifier complete_ti =
makeTypeIdentifier(complete_to, typeid_encoding_);
hash_type_identifier_map_[type] = {minimal_ti, complete_ti};
minimal_type_map_[minimal_ti] = minimal_to;
complete_type_map_[complete_ti] = complete_to;
}
}
Because the two Minimal alias TypeObjects are identical, makeTypeIdentifier()
returns the same Minimal equivalence hash for both aliases. The map assignment:
minimal_type_map_[minimal_ti] = minimal_to;
then leaves only one Minimal alias entry for both ScoreSeq and KeyAliasSeq.
3. Default generated Topic TypeSupport exposes only Minimal metadata
In dds/idl/typeobject_generator.cpp:1883-1920, the generated topic
TypeSupport always returns Minimal metadata:
// dds/idl/typeobject_generator.cpp
getMinimalTypeIdentifier<...>()
getMinimalTypeMap<...>()
In dds/idl/typeobject_generator.cpp:1922-1948, Complete metadata accessors
are generated only when produce_xtypes_complete_ is enabled:
if (produce_xtypes_complete_) {
getCompleteTypeIdentifier<...>()
getCompleteTypeMap<...>()
}
In default generation, code that consumes the topic TypeInformation sees the
collapsed Minimal TypeMap and does not get a Complete TypeMap that would contain
both alias names.
4. TypeSupportImpl publishes the default generated maps
dds/DCPS/TypeSupportImpl.cpp:97-130 uses the generated Minimal map and adds
the Complete map only if it exists:
void TypeSupportImpl::to_type_info(TypeInformation& type_info) const
{
to_type_info_i(xtypeinfo.minimal, getMinimalTypeIdentifier(), getMinimalTypeMap());
const XTypes::TypeIdentifier& complete_ti = getCompleteTypeIdentifier();
if (complete_ti.kind() != XTypes::TK_NONE) {
to_type_info_i(xtypeinfo.complete, complete_ti, getCompleteTypeMap());
} else {
xtypeinfo.complete = XTypes::TypeIdentifierWithDependencies();
}
}
void TypeSupportImpl::add_types(const XTypes::TypeLookupService_rch& tls) const
{
const TypeMap& minTypeMap = getMinimalTypeMap();
tls->add(minTypeMap.begin(), minTypeMap.end());
const TypeMap& comTypeMap = getCompleteTypeMap();
tls->add(comTypeMap.begin(), comTypeMap.end());
}
If Complete TypeObjects were not generated, getCompleteTypeIdentifier() is
TK_NONE and the alias-preserving Complete map is not advertised.
Expected Behavior
Please clarify the intended behavior for typedef aliases in default XTypes
metadata.
If OpenDDS intends Minimal TypeObjects to be purely structural, then this
canonicalization may be intentional, but it should be documented clearly:
- default Minimal TypeMaps may collapse distinct typedef aliases that differ
only by name; - alias names are not recoverable from default Minimal metadata;
- users who need alias-level schema names must generate Complete TypeObjects
with-Gxtypes-complete.
If OpenDDS intends default TypeLookup metadata to preserve schema-level typedef
aliases used by topic members, then the default metadata path should expose
enough information for both ScoreSeq and KeyAliasSeq to be discoverable.
Actual Behavior
Default OpenDDS generation exposes only:
Minimal TypeMap:
TK_STRUCTURE: test::AliasTypeMapProbe
TK_ALIAS: one collapsed alias for sequence<long, 3>
Expected alias-level schema view:
Aliases used by the topic:
test::ScoreSeq
test::KeyAliasSeq
Observed Complete TypeMap with -Gxtypes-complete:
Complete TypeMap:
TK_ALIAS: test::ScoreSeq
TK_ALIAS: test::KeyAliasSeq
TK_STRUCTURE: test::AliasTypeMapProbe
Impact
This does not corrupt the serialized data and does not cause value decoding
failure. The payload decodes correctly.
The impact is on type metadata correctness and schema introspection:
- dynamic type tools may not be able to reconstruct the IDL-level typedef
graph from default OpenDDS metadata; - schema comparison tools may incorrectly treat two role-distinct aliases as a
single alias; - TypeLookup/TypeInformation consumers may see less alias metadata from
OpenDDS than from implementations that expose alias declarations separately; - debugging or compatibility tooling cannot tell that
score_alias_probeand
key_alias_sequenceintentionally use different typedef names.
This is especially relevant for schemas that use typedef aliases as semantic
roles, for example ScoreSeq vs KeyAliasSeq, even when their wire layout is
identical.
Suggested Fix / Resolution Options
Possible fixes or clarifications:
- Document the behavior as intended Minimal TypeObject canonicalization and
explicitly recommend-Gxtypes-completefor alias-preserving metadata. - Emit a warning or diagnostic when default generation collapses multiple
typedef aliases with identical Minimal TypeObjects. - Generate Complete TypeObjects by default when a topic type uses typedef
aliases whose names would otherwise be lost in Minimal metadata. - If OpenDDS expects default TypeLookup metadata to preserve typedef names,
include the Complete alias TypeObjects, or equivalent alias-name dependency
metadata, in the default advertised type metadata.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Run pocs/opendds_typedef_alias_typemap/reproduce.sh from the repository root to compare default and -Gxtypes-complete generation. Read dds/idl/typeobject_generator.cpp around generate_alias_type_identifier() and update_maps(), then inspect dds/DCPS/TypeSupportImpl.cpp. Done means establishing the intended Minimal TypeMap behavior for distinct typedef aliases and documenting or correcting the default path accordingly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100