OMG IDL 4.2 — negative list
Spec: OMG IDL 4.2, formal/18-01-05,
132 pages. PDF in the local cache at
internal/standards/cache/omg/idl-4.2.pdf (gitignored).
Tool: tools/idl-4.2-gap-scan/scan.sh. Evidence:
tools/idl-4.2-gap-scan/cases.tsv (357 probes) and
tools/idl-4.2-gap-scan/expected.tsv (the recorded state).
The other pages under docs/spec-coverage/ are positive lists: they name
what was looked at and how far it got. What was never looked at cannot
appear in them, because their content comes from the tree and not from
the specification. This page is the other direction. Its denominator is
Annex A of the specification — all 227 grammar productions — plus the 24
standardized annotations of clause 8 and the normative clauses of 7.2
(lexis), 7.3 (preprocessing), 7.4.13 and 7.4.15 (semantics of bit sets,
bit masks and annotation application) and 7.5 (names and scoping).
Every row here has an attempt behind it: the IDL that was fed in and what came back. No row rests on a guess.
How it was measured
One minimal .idl file per construct, through all six subcommands of
zerodds-idlc. A construct can be missing from any one of them
independently, and the first version of this survey measured only check
and generate — the other four stood there as an assumption. That is the
same kind of positive list this page was written against, so the
measurement is now a matrix of probe × subcommand:
| Column | Command | Question |
|---|---|---|
check |
zerodds-idlc check |
does the front end recognize the construct |
strict |
zerodds-idlc check --strict |
does --strict change that |
lint |
zerodds-idlc lint |
does it pass the rules a valid file can still break |
format |
zerodds-idlc format |
is there a canonical form |
trip |
format, reparse, compare |
does the round trip hold |
dump |
zerodds-idlc dump-ast |
does the model show the construct |
deps |
zerodds-idlc print-deps |
does the dependency walk carry it |
rust |
zerodds-idlc generate --lang rust |
is there an emission |
c |
zerodds-idlc generate --lang c |
the same in the second backend |
trip is the column against a silent loss. A construct the parser knows
and the formatter does not disappears when a file is reformatted: no
error, just a file with something missing from it. Comparing the two
formatted texts does not catch that — the second format of a text that
already lost the construct reproduces the loss. So the comparison is over
the model: dump-ast and print-deps of the source against
dump-ast and print-deps of the formatted output, plus idempotence of
the format itself. - stands where there is nothing to compare, because
format or check refused the file.
Plus one measurement that does not depend on the tool: fixtures counts
how many of the 748 committed *.idl files in the tree contain the
construct at all, by a text pattern (column witness in cases.tsv).
That is weaker than “there is a test for it” — a zero is nevertheless a
fact: no IDL in the tree carries the construct, so nothing in the tree
can have exercised it. A - means no text pattern isolates the
construct; there the fixture question is not measurable this way and
stands as unchecked.
The number
357 probes. The findings fall out like this — F overlaps the other
categories, because the same probe can have one finding in check and a
different one in format:
| Category | Probes | |
|---|---|---|
| A | not recognized — the front end rejects it | 14 → 13 → 0 |
| B | accepted although the specification requires a rejection | 19 → 2 |
| C | recognized, not emitted | 12 → 75 → 74 |
| D | emitted, but no .idl in the tree contains it |
33 → 26 → 0 |
| E | --strict has no effect |
4 → 0 |
| F | format accepts what check refuses |
21 → 0 |
| G | format refuses what check accepts |
3 → 0 |
A is empty: the three grammar forms of A.1 to A.3 and the eight
preprocessor forms of A.5 pass check. C grew by 63 probes without
anything being overlooked: the C column refuses every interface since
fbbed5a2230 made an interface binding refuse what it cannot place in
full, and the Rust column refuses the object type a port or connector
carries (§C below); the union with no default: is out of C (§C). G
came from A.5 and is empty: format checks the file as check does,
after the preprocessor (§G).
At production level, counted over the probes of category grammar in
cases.tsv (161 productions carry a probe): 73 have a finding — none a
rejected form, 64 a form with no emission, 14 a form with no fixture
(with overlaps). Before the re-recording the same count gave 27 (3, 12,
14). The figures of earlier versions of this page (227 productions, 37
with a finding) were bounded differently and do not compare with these.
Category B is none of the four categories the audit asked for. It had to be added, because the front end can miss not only what the specification allows but also what it forbids — and in the second case the result is not an error message but a model with wrong content. Three of the 19 rows are exactly that: the declared type of a constant disappears and is replaced by the type of the literal.
The matrix
Where each subcommand stands over all 357 probes. Per probe it is in
tools/idl-4.2-gap-scan/expected.tsv, one row per probe and one column
per subcommand — the evidence this page cites.
| Column | ok |
err |
- |
|---|---|---|---|
check |
324 | 33 | — |
strict |
321 | 36 | — |
lint |
324 | 33 | — |
format |
321 | 36 | — |
trip |
321 | — | 36 |
dump |
324 | 33 | — |
deps |
324 | 33 | — |
rust |
307 | 50 | — |
c |
249 | 108 | — |
check, lint, dump and deps agree probe for probe: all four go
through analyse(). format stands three probes below, which is G.
strict stands three probes below — the three vendor directives of E.
trip is - wherever check or format refused the file and ok
everywhere else: no silent loss in the formatter.
rust and c stand below, and that is category C: 50 and 108 probes
respectively that the front end recognizes and a backend deliberately
does not emit.
The fixture counts in the tables of this page are from before the
re-recording, counted over 748 files; expected.tsv carries them over
the 821 files the tree has now.
A — not recognized
The specification allows it; zerodds-idlc check rejects it.
A.1 (17) <literal> — adjacent string literals (closed)
Now: ok in all nine columns (811b31a6685). Evidence:
crates/idl-lower/tests/idl_4_2_grammar.rs. The finding as it stood:
Spec: §7.2.6.3 — “Adjacent string literals are concatenated. Characters in concatenated strings are kept distinct.”
IDL: const string K = "ab" "cd";
Result: error: [IDL0002] expected ';' after the constant (1:23),
followed by [IDL0001] expected a declaration.
Fixtures: 0 of 748.
Probe: bb1-017-lit-string-concat
A.2 (64) <type_declarator> — <constr_type_dcl> inside a typedef (closed)
Now: ok in all nine columns (811b31a6685). Evidence:
crates/idl-lower/tests/idl_4_2_grammar.rs. The finding as it stood:
Spec: §7.4.1.4.4.7 “Naming Data Types”, production 64 —
<type_declarator> ::= { <simple_type_spec> | <template_type_spec> | <constr_type_dcl> } <any_declarators>.
The third alternative defines a struct, union or enum inside the
typedef itself.
IDL: typedef struct Inline { long m; } Ali;
Result: error: [IDL0001] expected a type (1:9), three follow-on errors.
Fixtures: 0 of 748.
Probe: bb1-064-typedef-constr
A.3 (112) <export> ::+ … — typeid, typeprefix and import in an interface body (closed)
Now: check to deps and rust ok (811b31a6685); c refuses
it as it refuses every interface (category C). Evidence:
crates/idl-lower/tests/idl_4_2_grammar.rs. The finding as it stood:
Spec: §7.4.6.3, production 112 —
<export> ::+ <type_id_dcl> ";" | <type_prefix_dcl> ";" | <import_dcl> ";" | <op_oneway_dcl>.
At file scope all three work (probes bb6-113-typeid,
bb6-114-typeprefix, bb6-115-import-scoped, all ok). Only the
extension of <export> is missing — the form inside an interface body.
IDL / result:
| IDL | first error |
|---|---|
interface Ia { typeid Ia "IDL:Ia:2.0"; }; |
[IDL0001] expected a type (1:16) |
interface Ia { typeprefix Ia "ex.org"; }; |
[IDL0001] expected a type (1:16) |
interface Ia { import Mb; }; |
[IDL0001] expected a type (2:19) |
Fixtures: not measurable by text search.
Probes: bb6-112-export-typeid, bb6-112-export-typeprefix,
bb6-112-export-import
A.4 §7.4.15.4.2 — unknown annotation with an identifier argument (closed)
Spec: §7.4.15.4.2 — “in case an unknown annotation is encountered, it should be ignored by the IDL-processing tools.”
Two of the three forms were ignored, as the clause requires. The third was not:
| IDL | before | now |
|---|---|---|
@nosuchthing struct Sa { long m; }; |
0 errors | 0 errors |
@nosuchthing(v = 1) struct Sa { long m; }; |
0 errors | 0 errors |
@nosuchthing(v = SOME_NAME) struct Sa { long m; }; |
[IDL0010] a name does not resolve: … ["SOME_NAME"] |
0 errors |
An identifier used as an argument value was resolved even though the
annotation it belongs to is to be ignored. Every file writing
@RTI_topic_kind(NO_KEY) or @allowed_data_representation(XCDR2) failed
on it — the form vendor annotations use for their enum values. This is
not a survey case: it is the IDL users hand the tool.
The fix. Lowering::annotation in
crates/idl-lower/src/annotation.rs decides whether the annotation is
known — in BUILTIN_ANNOTATIONS, or bound in scope as an
AnnotationDef. If it is not, an argument whose value is an identifier
is not lowered as an expression. The mechanism already existed for the
builtins with an enumerated parameter (@autoid(HASH),
@extensibility(FINAL)) and now carries both cases: the identifier
belongs to the annotation’s own type and is in no scope of the file.
A literal is still lowered — @nosuchthing(v = 1) keeps its 1. Only
an identifier has nothing in this file to point at.
A second defect of the same family, fixed with it. @verbatim was
missing from the list of builtins with an enumerated parameter, although
IDL 4.2 §8.3.5.1 declares PlacementKind placement. Every
@verbatim(placement = BEFORE_DECLARATION) in the tree was refused over
the enumerator. verbatim_annotation reads all three parameters out of
Argument::text and Argument::identifier anyway, so nothing is lost.
crates/idl-parse/tests/fixtures/spec_features/rti_connext_pragmas.idl
carried both defects in one file and now passes check.
Tests: tools/idlc/tests/annotation_arguments.rs — six tests at the
compose level, because the diagnostic came from the resolve pass. The
first draft sat in idl-lower and would have been green with or without
the fix; a mutation showed that.
Mutation: switching the condition off (3 red), discarding literals too
(1 red), taking verbatim off the list (2 red), treating a declared
annotation as unknown (1 red).
Probe: sem-7.4.15-unknown-annotation-enum-arg — now ok.
A.5 §7.3 — the preprocessor is not the one from ISO/IEC 14882:2003 (closed)
Now: all eight probes ok in check, lint, dump, deps, rust
and c (9303a2a33cf): function-like macros, #, ## with
placemarkers, the rescan with hide sets, #if over a full constant
expression, __LINE__, __FILE__, line continuation. New code
IDL0022. Evidence: crates/idl-parse/tests/preprocessor_iso_14882.rs.
format refuses three of them, see G. The finding as it stood:
Spec: §7.3 — “IDL shall be preprocessed according to the specification of the preprocessor in ISO/IEC 14882:2003.”
Six forms of that preprocessor are missing. Two of them report themselves with their own diagnostic — the better half of the finding, because a silent failure here would have swallowed text.
| IDL | Result |
|---|---|
#define TWICE(x) ((x) * 2) + const long K = TWICE(3); |
[IDL0002] expected ';' after the constant (1:22) — function-like macros are not expanded |
#define CAT(a, b) a##b + struct CAT(S, a) { … }; |
[IDL0002] expected '{' opening the struct body (1:12) — ## |
#define STR(x) #x + const string K = STR(ab); |
[IDL0002] expected ';' after the constant (2:4) — # as the stringize operator |
#define NN \ with a continuation line 4 |
[IDL0001] expected a constant expression (3:12) — backslash line continuation |
#define AA 1 + #define BB AA + const long K = BB; |
[IDL0006] macro 'BB' expands to 'AA', which is itself a macro; nested expansion is not performed |
#if 2 * 3 > 5 |
[IDL0006] '#if 2 * 3 > 5' is not a form this preprocessor evaluates |
const long K = __LINE__; |
[IDL0010] a name does not resolve: … ["_LINE__"] — there are no predefined macros |
const string K = __FILE__; |
[IDL0010] a name does not resolve: … ["_FILE__"] |
What does work, recorded here so that it stays that way: #include in
both forms, #define without parameters, #undef, #ifdef, #ifndef,
#if/#else/#elif/#endif with a constant, defined(X), #line,
#pragma, #pragma once.
Probes: lex-7.3-define-function, lex-7.3-token-paste,
lex-7.3-stringize, lex-7.3-line-continuation, lex-7.3-nested-macro,
lex-7.3-if-arithmetic, lex-7.3-predefined-line,
lex-7.3-predefined-file
B — accepted although the specification requires a rejection
There is no error message here, there is a result. In the first three rows that result was demonstrably wrong; those three are closed.
B.1 §7.4.1.4.3 — the declared type of a constant (closed)
Spec: §7.4.1.4.3 “Constants” — a constant declaration consists, among
other things, of “a value expression (<const_expr>), which shall be
consistent with the type declared for the constant”. §7.2.6.3 —
“Attempts to assign a wide string literal to a non-wide string constant
or to assign a non-wide string literal to a wide string constant result
in a compile-time diagnostic.”
The declared type was discarded; the literal’s type replaced it:
| IDL | check before |
emitted Rust before | check now |
|---|---|---|---|
const long K = "x"; |
0 errors | pub const K: &'static str = "x"; |
[IDL0019] |
const string Ss = L"x"; |
0 errors | pub const Ss: &'static str = "x"; |
[IDL0019] |
const wstring Ss = "x"; |
0 errors | pub const Ss: &'static str = "x"; |
[IDL0019] |
Someone who wrote const long and got a &str learned about it from the
Rust compiler at the use site, not here.
The fix. Lowering::check_const_value_form in
crates/idl-lower/src/lower.rs compares the form of the value with the
declared type. The comment at that spot already claimed the rule — “the
expression is checked against it rather than used to derive it” — with no
check underneath it. New diagnostic code IDL0019 TypeMismatch; codes
are appended, never renumbered.
Where the check stays silent, and why. It fires only when the
declared type resolves here to a primitive, a string/wstring or a
fixed, and the expression settles to one literal form. A named type,
an alias, an enumeration, a reference to another constant and an
unresolved reference all pass without a word. A rejection that reaches
too far breaks valid IDL, which costs more than the defect it removes.
Measured: none of the 748 committed *.idl files reports IDL0019.
Tests: crates/idl-lower/tests/validation.rs —
a_constant_whose_value_is_not_of_its_declared_type_is_refused,
the_wide_and_narrow_string_pair_is_the_case_the_clause_names and
every_form_the_specification_admits_still_gets_through (28 forms that
have to keep getting through). Mutation: if admitted || true turns the
first two red, _ => (false, …) in place of _ => return the third.
Probes: scope-7.4-const-type-mismatch,
scope-7.2.6.3-wide-to-narrow, scope-7.2.6.3-narrow-to-wide — all
three now err in all nine columns.
B.2 §7.5.2 — three scoping rules (one closed, two deliberately open)
Spec: §7.5.2 — “The name of a module, structure, union, map,
interface, value type, event type, exception or home may not be redefined
within the immediate scope of the module, …”. The specification gives
both cases as examples with the comment // Error.
| IDL | Spec says | before | now |
|---|---|---|---|
module Ma { typedef short Ma; }; |
Error (example §7.5.2) | 0 errors | [IDL0008] |
interface Ia { void ia(in short jj); }; |
Error (example §7.5.2) | 0 errors | 0 errors — open |
module Inner2 { typedef Inner1::Ss1 Ss2; typedef string inner1; }; |
Error (example §7.5.2) | 0 errors | 0 errors — open |
The first is closed. check_redefines_enclosing_scope in
crates/idl-lower/src/lower.rs, called from check_collision, compares
a declaration’s name with the name of the scope it is written in —
case-insensitively, because §7.2.3 makes every identifier comparison in
IDL so.
Immediate scope only, and that is the whole of the rule’s safety:
module Ma { module Mb { typedef short Ma; }; }; stays legal, because
Ma is not the name of the scope Mb. The difference between this rule
and one that breaks valid IDL is exactly there.
The rule confirms a decision the tree had already made: commit
4051da478d0 recorded that module common { struct common { … } } is
not valid IDL and corrected comments in the Java and TypeScript adapters
that claimed the opposite. The interop case
tests/interop-corpus/resolve-module-named-as-type/type.idl measures
which vendors refuse it; it is now on the corpus test’s exception list.
Two stay open, with reasons.
- The operation that clashes with the interface name. §7.5.2 lists,
for the redefinition rule, “module, structure, union, map, interface,
value type, event type, exception or home” — operations are not among
them; that case comes from the introduction example. Whether the rule
covers every
<export>or only operations is interpretation, and reaching too far breaks CORBA interfaces. - The “identifier introduction” rule. It requires tracking every use
of a name per scope, case-insensitively. That is the highest
over-reach risk on the whole list. Both counter-probes
(
scope-7.5.2-not-introduced-ok,scope-7.5.2-rooted-no-introduce) pass today — correctly, but for the wrong reason, because the rule does not exist. An implementation needs considerably more counter-probes than those two.
What §7.5.2 otherwise does hold: module reopening, redefinition in a
nested scope, enum names in the enclosing scope
(scope-7.5.2-enum-name-in-scope is correctly refused), and the case
collision rule of §7.2.3, which even checks identifiers against keywords.
Tests: crates/idl-lower/tests/validation.rs —
a_declaration_may_not_reuse_the_name_of_its_own_scope and
only_the_immediate_scope_is_compared.
Mutation: switching the condition off (1 red); replacing the scope name
with the declaration’s own, so every declaration is hit (33 red).
Probes: scope-7.5.2-redefine-own-name — now err;
scope-7.5.2-op-clashes-iface and scope-7.5.2-introduced-ident stay
ok.
B.3 §7.4.1.4.4 — direct recursion (closed)
Spec: §7.4.1.4.4.4.4 — a structure may contain itself only across a
sequence (or an @optional member, or a map).
IDL: struct Sa { Sa m; };
Before: check reported 0 errors. generate reported
SpecRefuses { detail: "NodeId(3) closes a type cycle with nothing on it that terminates a value — no sequence, no map and no @optional member — which IDL 4.2 §7.4.1.4.4.4.4 does not admit; …" }.
So the rule was there and cited the clause; it just sat in the layout
stage rather than the front end. Someone using check to find out
whether a file is valid IDL did not find out.
The fix — extracted, not rebuilt.
zerodds_ast_layout::types_that_close_an_inadmissible_cycle runs the
same plan walk and keeps exactly that one error. It is explicitly
not a second implementation of the cycle test: the barrier
bookkeeping — a cycle is legal exactly when a sequence, a map or an
@optional member lies on it — lives in the descent, and a rule written
twice drifts. Every other LayoutError is dropped: an unresolved
reference is the resolve pass’s to report, and an encoding that cannot
carry a type is generate’s.
It is called from zerodds_idl_compose::compose_source_with, after the
resolve pass (a cycle is a property of the resolved type graph). Every
caller of the front end therefore gets the same answer — check, a
build.rs through zerodds-build, a wire probe — rather than only the
CLI. ast-layout’s own behaviour is unchanged.
The message names the type in full. The layout stage’s named a NodeId,
which outside a debugger is worse than nothing.
Admissible recursion stays admissible, and that is the more expensive
half of the rule: sequence<Sa>, sequence<Sa, 4>, @optional Sa,
map<long, Sa> and mutual recursion across sequences all still get
through. The interop corpus holds seven recursion cases; exactly one —
recursive-array — is the inadmissible form, and the test reads all
seven off disk rather than restating them.
Tests: crates/idl-compose/tests/recursion.rs, four tests.
Mutation: commenting the call out (3 red), keeping the wrong
LayoutError in the extractor (3 red).
Probe: scope-7.4-recursive-struct — now err.
B.4 §7.4.13 — seven bounds on bit sets and bit masks (closed)
Spec: §7.4.13.4.3.2 — “The first one (<positive_int_const>) is the
number of bits that can be stored (its size). The maximum value is 64”;
“When the destination type is given, the number of stored bits cannot
exceed its size (i.e., 1 for boolean, 8 for octet, …)”. §7.4.13.4.3.3 —
“A bit mask must contain at least one value and no more than its size
expressed in bits”; “By default, the size of a bit mask is 32”;
@bit_bound “must be lower than or equal to 64. Accordingly the number
of values cannot then exceed the value given to @bit_bound”;
@position “Possible positions range from 0 … up to (size – 1)”.
| IDL | before | now |
|---|---|---|
bitset Ba { bitfield<65> f1; }; |
check 0 errors; only the C writer refused: NoWriter { … "bitset wider than the sixty-four bits an anchor holds" } |
[IDL0014] |
bitset Ba { bitfield<9, octet> f1; }; |
0 errors, emitted in both languages | [IDL0014] |
bitset Ba { bitfield<2, boolean> f1; }; |
0 errors, emitted in both languages | [IDL0014] |
@bit_bound(65) bitmask Ba { F1 }; |
0 errors | [IDL0014] |
@bit_bound(2) bitmask Ba { F1, F2, F3 }; |
0 errors | [IDL0014] |
bitmask Ba { F01 … F33 }; (33 values, default size 32) |
0 errors | [IDL0014] |
@bit_bound(2) bitmask Ba { @position(5) F1 }; |
0 errors | [IDL0014] |
The one bound the front end did hold is the lower one: bitmask Ba { };
is refused with [IDL0002] a 'bitmask' needs at least one bit value.
The fix. Lowering::check_bit_bounds in
crates/idl-lower/src/complete.rs, called from complete() rather than
from the declaration: @bit_bound and @position are annotations and
are not applied until the file is fully lowered. Diagnostic code
IDL0014 InvalidBound, the existing code for a bound outside its legal
range.
The destination widths are the ones §7.4.13.4.3.2 names: 1 for
boolean, 8 for octet/int8/uint8, 16, 32, 64. A destination type
that does not resolve here, or is none of those, produces no diagnostic —
the same rule as in B.1: stay silent where the answer is not settled.
One file in the tree is affected.
tools/wire-oracle/cases/bitmask_past_bound/type.idl writes
@bit_bound(4) with @position(7), deliberately: the case measures what
other vendors’ generators do with a position outside the range.
tools/byte-gate/gate.sh hands it only to Cyclone, Fast DDS, OpenDDS and
RTI — the ZeroDDS side does not compile it — so the measurement it
carries is unchanged. No test in the tree runs check over every
committed .idl.
Tests: crates/idl-lower/tests/validation.rs — five tests, one per
rule, plus the_bit_forms_the_specification_admits_still_get_through
with nine forms that have to keep getting through.
Mutation: if false && … in front of each of the four comparisons turns
one test red apiece; MAX_BITFIELD_BITS/MAX_BIT_BOUND from 64 to 63
turns three red.
Probes: sem-7.4.13-bitfield-over-64,
sem-7.4.13-bitfield-dest-octet, sem-7.4.13-bitfield-dest-bool,
sem-7.4.13-bitbound-over-64, sem-7.4.13-bitmask-over-bound,
sem-7.4.13-bitmask-over-default, sem-7.4.13-position-out-of-range —
all seven now err.
B.5 §7.4.15.4.2 — four rules on applying an annotation (closed)
Spec: §7.4.15.4.2 — “Members with no default value must be given a value”; “A constant expression, whose type must be compatible with the member’s declaration”; “In case there is only one member, the annotation application may be shortened to the name of the annotation prefixed with @ and followed with the constant value of that unique member”; and finally “malformed definitions shall be treated as an error”.
| IDL | before | now |
|---|---|---|
@annotation Aa { long v; }; + @Aa struct … — required member, no value |
0 errors | [IDL0007] |
@Aa("x") on long v default 0 — type does not fit |
0 errors | [IDL0007] |
@Aa(nosuch = 1) — no such member name |
0 errors | [IDL0007] |
@Aa(1) on an annotation with two members |
0 errors | [IDL0007] |
The defect underneath was bigger than the four rules. None of them
could hold, because an annotation had no members in the model at all.
Rule (222) writes an annotation member’s name as a <simple_declarator>,
and parse_annotation_decl reads it with a bare expect(Ident) and no
Declarator node. Lowering::declared walks Declarator children,
found none, returned an empty list — and every member of every
annotation vanished without a word. @annotation Aa { long v; };
reached the model with zero members.
annotation_decl now reads the name directly (first_ident reads the
node’s own tokens and not its descendants’, so the name of Ea v; is v
and not Ea) and reports a member with no name instead of dropping it.
And the body is its own scope. Rule (221) admits an enum, a const
and a typedef beside the members —
@annotation Mode { enum Kind { READ, WRITE }; Kind kind default READ; };.
Those were parsed and never lowered, which nothing noticed while the
members themselves were being dropped. With the members read, a member’s
type had nothing to resolve against:
crates/idl-parse/tests/fixtures/spec_features/user_annotations.idl fell
over at once. annotation_decl now pushes a scope and lowers the three
admitted forms inside it.
The boundary against the ignore rule is the clause’s own. The same
§7.4.15.4.2 requires an unknown annotation to be ignored. None of the
four rules therefore reaches an annotation the file did not declare:
check_annotation_application is called only with a resolved
declaration. @RTI_topic_kind(NO_KEY) is untouched.
The type check is the one from B.1.
Lowering::value_disagrees_with answers both clauses — §7.4.1.4.3 for a
constant’s value and §7.4.15.4.2 for an annotation argument. One reading,
so the two cannot drift apart.
Left open because nothing settles it: whether an enumerator declared
inside an annotation body may be written unqualified at an application
site — @Mode(WRITE). Neither §7.5 nor rule (221) says, and no fixture
in the tree exercises it. So no assertion about it stands in the test.
Tests: crates/idl-lower/tests/validation.rs — five tests, one per
rule plus none_of_the_four_rules_reaches_an_unknown_annotation and
an_annotations_declared_members_reach_the_model;
tools/idlc/tests/annotation_arguments.rs for the body scope.
Mutation: switching each of the four conditions off in turn (1 red
apiece), removing the call entirely (4 red).
Probes: sem-7.4.15-missing-required,
sem-7.4.15-param-type-mismatch, sem-7.4.15-unknown-param-name,
sem-7.4.15-shorthand-two-members — all four now err.
sem-7.4.15-unknown-annotation and -unknown-annotation-args stay ok;
they are the counter-probes and not a finding.
B.6 §7.3 — #error (closed)
Spec: IDL 4.2 §7.3 adopts the preprocessor of ISO/IEC 14882:2003 wholesale. Its §16.5: “#error causes the implementation to produce a diagnostic message that includes the specified sequence of preprocessing tokens.”
IDL: #error stop here
Before: check reported 0 errors; generate --lang rust wrote a
file with no content. The directive was passed over in silence — the one
directive that exists to stop a build was the only one that could not.
Now: [IDL0020], with the tokens in the message as the clause
requires. A new diagnostic code, appended; IDL0019 stays
TypeMismatch, because codes are never renumbered.
The rule’s only subtlety is if keeping: an #error inside a branch
the conditionals cut out is text that was never reached. Reporting it
would refuse every file that guards one — which is nearly every file that
has one at all.
Tests: crates/idl-parse/tests/preprocess.rs — three tests, among
them an_error_directive_in_a_branch_that_is_cut_out_says_nothing over
#ifdef, #else and #if 0.
Mutation: not reporting the directive (2 red); removing if keeping, so
it reports in the dead branch too (1 red).
Probe: lex-7.3-error-directive — now err.
C — recognized, not emitted
All eleven rows are deliberately refused: in every case the backend names the reason and, where there is one, the clause. That is not a gap in the sense of “overlooked”, but it has to stay distinguishable, because an application that uses the construct gets nothing.
| Construct | Production | Rust | C | Reason, as the tool states it | Fixtures |
|---|---|---|---|---|---|
any as a member |
69, 70 | — | — | NoLanguageForm { construct: "any" }, in C additionally NoWriter { … "'any', which DDS-XTypes has no encoding for; only CORBA CDR writes one, as a TypeCode ahead of the value" } |
9 |
typedef any |
70 | — | — | ditto | 1 |
any as an annotation member |
223, 224 | ok | — | as above | – |
Object |
117, 118 | — | — | NoLanguageForm { construct: "Object" } |
4 |
ValueBase |
131, 132 | — | — | NoLanguageForm { construct: "ValueBase" } |
1 |
fixed<9,2> |
42 | — | ok | NoWriter { member: "m", kind: "fixed" } |
17 |
valuetype Va string; (value box) |
125, 126 | ok | — | NoWriter { … "boxed value, whose presence flag has no settled wire form" } |
1 |
abstract valuetype |
125, 127 | ok | — | NoLanguageForm { … "an aggregate with no members — C23 §6.7.2.1p8 leaves a structure with no named member undefined" } |
0 |
abstract eventtype (definition) |
166, 168 | ok | — | ditto | – |
abstract eventtype (forward + definition) |
167 | ok | — | ditto | 1 |
struct Sa { }; (empty struct, BB Extended Data-Types) |
195 | ok | — | ditto | 3 |
Closed: the union with no default: whose defaulted discriminator
selects no branch (probe bb1-050-union-no-default, productions 49, 50)
is ok in the Rust column. The union’s enum has the variant
_NoMember(<discriminator>) for a discriminator that selects no member
(IDL 4.2 §7.4.1.4.4.4), and a variant whose case does not fix the
discriminator (several labels, default:) carries it ahead of the
member. Test:
crates/lang-rules/tests/rust_corpus_compiles.rs::a_union_keeps_its_discriminator_through_rustc_and_back.
The last four share a single cause: C has no form for an aggregate with
no members, and abstract valuetype, abstract eventtype and the empty
struct of production 195 all run into it. Rust emits all four.
The 17 fixtures with fixed<…> and the 9 with any are the hint that
both constructs occur in the tree but do not go through the Rust
emission.
C.2 The interface family in C, the object type at ports and connectors in Rust
63 more probes, all refused with a reason:
- C, 62 probes — every probe with an
interface, including one that only hangs off a component, a home or a port type: “C has no complete DDS-RPC 1.0 binding for an interface, and this model declares 1 …”, with the list of columns that have one. Sincefbbed5a2230an interface binding refuses what it cannot place in full rather than emitting part of it. Probes:bb3-074tobb3-095,bb4-097-export-*,bb5-103-value-supports,bb6-113-typeid,bb6-114-typeprefix,bb6-119-local-interface,bb6-120-oneway,bb6-123-op-with-context,bb7-129,bb7-130,bb8-135tobb8-143,bb9-145tobb9-150,bb10-155tobb10-165,bb11-173tobb11-182,ann-8.3.6.1-service*,ann-8.3.6.2-oneway,ann-8.3.6.3-ami*,scope-7.5.2-op-clashes-iface,scope-7.5.3-type-name-lookup. - Rust, 12 probes — a port (
provides,uses,port,mirrorport), a port type or a connector holds an object reference, and the Rust column has no form forObject:NoLanguageForm { construct: "Object", language: "Rust" }. Probes:bb8-141-provides,bb8-143-uses,bb10-157-interface-type-obj,bb10-158-uses-multiple,bb11-173tobb11-182.
Not determined: which commit after the last recording moved the twelve
Rust probes from ok to err; the refusal of Object itself is older.
G — format refuses what check accepts
zerodds-idlc format reads the token stream before the preprocessor, so
that comments and directives survive. An object-like macro stands there
as an identifier and is valid syntax; the invocation of a function-like
macro is not. format therefore refuses the file — with a diagnostic,
losing nothing — while check accepts it after expansion.
| IDL | format |
|---|---|
#define TWICE(x) ((x) * 2) + const long K = TWICE(3); |
[IDL0002] expected ';' after the constant |
Probes: lex-7.3-define-function, lex-7.3-token-paste,
lex-7.3-stringize.
Closed: zerodds-idlc format decides validity as check does, after
the preprocessor, and then lays the text out over the source’s tokens
(format::canonical_of_checked); a macro call stays the token sequence
it was written as. The three probes are ok in format and in the
trip round trip now. Test:
tools/idlc/src/format.rs::tests::a_function_like_macro_call_keeps_its_tokens.
D — emitted, but no .idl in the tree contains it (closed)
Both backends produce source, and none of the committed *.idl files
carried the construct. After the re-recording there were 26. Every one
now has a fixture under tools/idlc/tests/idl-4.2-gaps/, one per probe
and named after it, and tools/idlc/tests/idl_4_2_gaps.rs runs them
through zerodds-idlc check and zerodds-idlc generate --lang rust and
--lang c. Each fixture carries the probe’s own IDL, so the text
answers the fixture question scan.sh asks with its witness column.
Eleven had left earlier: @range, @min, @max, @unit, const octet
and union Ua; had gained a fixture in the tree; @ami,
interface Ia;, interface Ia : Ib, local interface and context
are no longer emitted by the C column (C.2). Four had joined that did not
pass check before: adjacent string literals, typedef struct … Ali;,
the function-like macro and ##.
The 26 that left:
| Construct | Production / clause | Probe |
|---|---|---|
const fixed |
6, 43 | bb1-006-const-fixed |
const wchar |
6 | bb1-006-const-wchar |
const wstring |
6 | bb1-006-const-wstring |
^ in a constant expression |
9 | bb1-009-xor-expr |
>> in a constant expression |
11 | bb1-011-shift-right |
~ as a unary operator |
15 | bb1-015-unary-tilde |
| fixed-point literal | 17 | bb1-017-lit-fixed |
| wide string literal | 17 | bb1-017-lit-wstring |
| adjacent string literals | 17 | bb1-017-lit-string-concat |
typedef struct … Ali; |
64 | bb1-064-typedef-constr |
valuetype Va; (forward) |
99, 110 | bb5-110-value-forward |
int64 |
212 | bb13-212-int64 |
| octal character escape | 7.2.6.2 | lex-7.2.6.2-char-escape-octal |
| hexadecimal character escape | 7.2.6.2 | lex-7.2.6.2-char-escape-hex |
| Unicode escape | 7.2.6.2 | lex-7.2.6.2-wchar-escape-unicode |
| floating point, leading dot | 7.2.6.4 | lex-7.2.6.4-float-leading-dot |
| floating point, trailing dot | 7.2.6.4 | lex-7.2.6.4-float-trailing-dot |
| fixed-point literal | 7.2.6.5 | lex-7.2.6.5-fixed-literal |
| function-like macro | 7.3 | lex-7.3-define-function |
#ifndef |
7.3 | lex-7.3-ifndef |
#if/#else |
7.3 | lex-7.3-if-else |
#elif |
7.3 | lex-7.3-elif |
defined(X) |
7.3 | lex-7.3-defined-operator |
#undef |
7.3 | lex-7.3-undef |
## |
7.3 | lex-7.3-token-paste |
#line |
7.3 | lex-7.3-line-directive |
Three of them were not measurable with the witness search before: the
patterns of the two character escapes and of the Unicode escape in
cases.tsv were over-escaped ('\\\\[0-7], '\\\\x, \\\\u…) and
did not match even the probe’s own IDL. They now stand as '\\[0-7],
'\\x and \\u[0-9a-fA-F] in the corpus.
E — --strict (closed)
tools/idlc/README.md and zerodds-idlc --help describe --strict as
“OMG IDL 4.2 only; reject every vendor extension”. The switch was parsed
and then dropped — tools/idlc/src/main.rs, analyse():
let _ = options.strict; — and Parser::over() wired
Dialect::ALL_VENDORS in regardless. Across all probes there was not one
where check and check --strict differed.
The path the dialect now takes. Options::dialect() in
tools/idlc/src/main.rs is the one place --strict is read, and every
one of the six subcommands goes through it — five via analyse(),
format via format::canonical_under. The dialect reaches both stages
vendor constructs live in: zerodds_idl_parse::parse_preprocessed_with
for the grammar and zerodds_idl_lower::Options::dialect for the
pragmas.
Measured, after the fix:
| IDL | Vendor | check |
check --strict |
|---|---|---|---|
keylist Sa (m); |
RTI Connext | 0 errors, 1 warning | 1 error |
#pragma keylist Sa m |
Cyclone DDS | 0 errors, 1 warning | 1 error |
#pragma DCPS_DATA_TYPE "Sa" |
OpenDDS | 0 errors, 1 warning | 1 error |
#pragma DCPS_DATA_KEY Sa m |
OpenDDS | 0 errors, 1 warning | 1 error |
@Key (the Fast DDS spelling) |
— | 0 errors | 0 errors |
The three directives are the ones the front end acts on: all three
make members keys, so a front end that ignores them builds a different
type from the same file. That is exactly the question --strict answers.
@Key stays admitted, and that is not a remainder of the gap. IDL
4.2 §7.4.15.4.2: “in case an unknown annotation is encountered, it should
be ignored by the IDL-processing tools.”
crates/idl-lower/src/annotation.rs recognizes annotations
case-sensitively, so @Key is not a known annotation, the front end does
not act on it, and refusing it under --strict would break conforming
IDL to gate nothing. The same holds for @RTI_* and @RTPS_*. Had
@Key been read as a key, it would be in the table above.
Tests: tools/idlc/tests/strict.rs — the matrix of six subcommands ×
four vendor extensions × two dialects, plus the counter-check that the
same files get through every subcommand without --strict;
crates/idl-lower/tests/dialects.rs,
strict_rejects_every_vendor_extension_the_front_end_acts_on and
strict_does_not_refuse_an_unknown_annotation.
Probes: dialect-rti-keylist, dialect-cyclone-pragma-keylist,
dialect-opendds-pragma-datatype — strict now err;
dialect-fastdds-key-capital stays ok.
One probe was built wrong. lex-7.3-pragma stood for §7.3 — for the
preprocessor handling #pragma at all — and used #pragma keylist Sa m
to do it, a Cyclone directive. It measured two axes at once and went red
under --strict for a reason that has nothing to do with §7.3. It now
writes #pragma vendorless Sa.
F — format accepted what check refused (closed)
Not a finding about a construct but about a subcommand. It only showed up when the survey went from two columns to the matrix over all six.
zerodds-idlc format did not run the pipeline check runs.
tools/idlc/src/format.rs, canonical(), calls
zerodds_idl_parse::parse(source) — the bare parser. No preprocessor, no
lowering stage, no resolve stage. The formatter refused a file that did
not parse; everything that only showed up after that went through
without a word. Measured: 21 of the 356 probes had check err and
format ok, and over the committed tree 12 of the 748 *.idl files. The
user got a formatted file back that their tool discarded on the next
step.
| Stage that reports the error | Probes |
|---|---|
| Preprocessor | lex-7.3-line-continuation, lex-7.3-nested-macro, lex-7.3-if-arithmetic, lex-7.3-predefined-line, lex-7.3-predefined-file |
| Lowering (lexical/scoping) | lex-7.2.3-case-collision, lex-7.2.3-keyword-collision, scope-7.5.2-enum-name-in-scope, scope-7.4-dup-case-label, scope-7.4-two-defaults, scope-7.4-enum-dup |
| Lowering (semantic) | sem-7.4.13-bitmask-empty, sem-7.4.1-seq-bound-zero, sem-7.4.1-string-bound-zero, sem-7.4.1-array-size-zero, sem-7.4.1-array-size-negative, sem-8.3.1.1-id-collision, sem-8.3.1.2-autoid-bad-arg, sem-8.3.1.6-ext-bad-arg, sem-8.3.1.3-key-and-optional |
| Resolve | scope-7.4-unknown-type |
The fix. run_format in tools/idlc/src/main.rs now goes through
analyse() first — the pipeline check runs — and refuses the file as
soon as an error is reported. Nothing is printed on the success path: the
formatted text goes to stdout, and a warning ahead of it would land in
the file a caller redirected it into.
All 21 probes now stand at format err. The library function
format::canonical_under keeps its parse-level contract; the corpus test
in tools/idlc/src/format.rs still measures through it.
The other direction does not occur: no probe has check ok and
format err. lint, dump-ast and print-deps go through analyse()
the way check does and agree with it on all 357 probes.
Round trip: over the 325 probes both check and format accept it
holds completely — same model, same dependencies, idempotent format. No
silent loss in the formatter. That explicitly includes the 26 probes
that made up category D until this fixture work: the formatter loses
none of them.
The tree itself
The survey measures probes. Up to here this page said nothing about
whether the tree’s own 748 committed *.idl files are valid IDL — and
there was no test running check over all of them. A construct that
went wrong in one of our own files only showed up if some other test
happened to read it.
What surfaced it was tools/wire-oracle/cases/bitmask_past_bound/type.idl:
deliberately out of spec, handed only to other vendors’ generators, and
indistinguishable from a file that had quietly gone wrong. It took a new
rule (B.4) to make it visible at all.
tools/idlc/tests/corpus.rs now runs check over every committed
.idl. 13 files are refused, all 13 with a written reason in
INTENDED — the allowed codes per file, and a sentence saying what the
file is for. Three groups:
| Group | Files | What they are |
|---|---|---|
| Probe inputs | 6 | tools/hashid-gate (2), tools/inventory-probes (2), tools/try-construct-gate (2) — the refusal is the measurement |
| Grammar fixtures | 4 | ccm_components, corba_value_repository, fastdds_pragmas, zerodds_security — curated for syntax coverage, not for a model that closes |
| Wire measurement | 1 | bitmask_past_bound — asks four other vendors what they do with a forbidden @position |
| ORB corpus | 1 | 05-typecode — CORBA::TypeCode, which every ORB predeclares |
| RTI fixture | — | was the 13th; passes since A.4 |
A second test checks the other direction: an entry whose file passes again is stale and turns the test red. An exception with no reason it refuses outright.
One real defect came out of it.
examples/demos/perf-camera-dds/idl/camera.idl wrote unsigned octet quality_hint; — unsigned octet is in no production of IDL 4.2, and
octet is unsigned already. A shipped example no conforming front end
compiles. Corrected to octet quality_hint;.
What this survey does not cover
- Emission in two languages only.
rustandc. The remaining backends undercrates/lang-rules/src/default_*.rsare unchecked. - No wire comparison. What was measured is whether source is produced, not whether the resulting bytes are right. That is the question the conformance fixtures answer, not this page.
fixturesis a text search. A hit does not prove the file is used in a test. A zero proves the construct does not occur in the tree.- No comparison against other front ends. Whether
fastddsgen, Cyclone’sidlcortao_idlaccept the same IDL is not measured. - Clause 9 (profiles). The four CORBA/CCM and three DDS profiles are
compositions of the building blocks; whether
zerodds-idlcneeds a profile argument is a product question and not a gap against the grammar. Unchecked. - Table 7-6 as a whole. The specification lists 84 keywords. One
representative is checked (
typedef boolean BOOLEAN;is rejected with[IDL0017]) along with the escape form (long _long;is accepted, and 150 fixtures in the tree use it). The other 83 keywords are not probed individually as identifiers. §7.2.1 Tokens and §7.2.5 Punctuation are likewise not covered character by character.
The tool
tools/idl-4.2-gap-scan/scan.sh measure and compare
tools/idl-4.2-gap-scan/scan.sh --record rewrite expected.tsv
tools/idl-4.2-gap-scan/scan.sh --report print the full measurement
IDLC=/path/to/zerodds-idlc tools/idl-4.2-gap-scan/scan.sh
Exit codes:
| 0 | measured; the gap is exactly what expected.tsv records |
| 1 | a probe that used to get through no longer does — the gap grew |
| 2 | nothing measured: no zerodds-idlc. Never a pass. |
| 3 | a probe now gets through that expected.tsv records as failing — the gap shrank and this page is behind the tree |
| 4 | cases.tsv and expected.tsv name different probes |
Runtime on an M1: 36 seconds for 356 probes over 748 .idl files.
Before the matrix it was 16 seconds with two subcommands measured; the
eleven invocations per probe cost the rest.
Exit 3 is not cosmetic. A negative list that still carries a closed gap as open is worse than none: it costs trust in the remaining rows.
Method note
Transferable to CORBA 3.3, the DDS specs and the 47 own specs under
docs/specs/ — at very different costs.
What was cheap. The denominator. IDL 4.2 has Annex A: a consolidated,
numbered grammar from which pdftotext -layout and twenty lines of
Python pull all 227 productions without a hole (gap check: range(1, 228)
against the parsed numbers). Without that annex, gathering the
productions out of fifteen building-block clauses would have cost a
multiple, and completeness would not have been checkable.
What was expensive. The probes. 356 minimal .idl files written by
hand, and two rounds of rework, because the first versions failed for the
wrong reason: an identifier Alias collides with the keyword alias,
and a union with no default: is refused by the backend for a reason
that has nothing to do with the production under test. Every mismeasure
looked like a finding. That is the actual work in a survey like this:
telling apart whether the probe or the tree is wrong.
Where the full text is needed. For categories A, C and D the grammar
is enough. For category B — accepted although forbidden — the prose is
needed, verbatim. All 19 B rows come from sentences that appear in no
production: “Adjacent string literals are concatenated”, “The maximum
value is 64”, “Members with no default value must be given a value”.
Without the PDF in full they would have been unfindable. And the same
clause refuted two claims that would otherwise have been written down as
gaps: unknown annotations should be ignored, and struct S { long s; }
is an error. Without the text those would have been two wrong rows.
What that means for the other specs.
| Corpus | Denominator available? | Estimated effort |
|---|---|---|
| CORBA 3.3 | Yes — Part 1 Annex A carries the same consolidated grammar, plus the GIOP/IIOP message types as tables. The prose, though, runs to about 1,150 pages across three parts, eight times IDL 4.2. | The grammar axis is roughly as here, one to two days. The B axis (forbidden forms) is the cost driver and has to be done per part. The probes need a different driver, because zerodds-idlc does not cover the whole surface: for GIOP/POA/IR the probe has to be a Rust test, not an .idl file. |
| DDS specs (DCPS 1.4, XTypes 1.3, DDSI-RTPS 2.5, Security 1.2) | Not in this form. The denominator there is not a grammar but a set of tables: QoS policies, submessage kinds, TypeKinds, plugin operations. They can be pulled out of the PDF mechanically, but each spec needs its own extractor. | One day per spec for the extractor plus the probes. The upside: the probes here are bytes, not text files, and tools/byte-gate and tools/unspecified-reach are the drivers that already exist. The four categories become: does the decoder know the field / does the encoder write it / is there a test vector / is it deliberately not implemented. |
Own specs under docs/specs/ |
Yes, and cheaper than all the others — the 47 documents are Markdown with numbered clauses, in the tree. One extractor for all 47. | The denominator is a matter of hours. The catch is a different one: a spec written in-house cannot be wrong against itself. What a negative list finds there is a clause with no implementation — useful, but not the same as a gap against a foreign denominator. The yield ranks behind CORBA and the DDS specs. |
The order I would recommend. DDS-XTypes 1.3 before CORBA 3.3: the denominator is smaller, the probes are bytes rather than text, and the wire area is the one where a missed construct silently delivers wrong data instead of an error message. The own specs last.
One rule the survey itself taught. “It does not occur in the tree” is no evidence for “it is not allowed” — that was the trigger. But the converse holds too: “the front end rejects it” is no evidence for “the specification forbids it”. Both directions need the spec text, and both came up in this survey.
Decision records
None. This page surveys and closes nothing.
OMG IDL 4.2 — Negativliste
Spec: OMG IDL 4.2, formal/18-01-05,
132 Seiten. PDF im lokalen Cache unter internal/standards/cache/omg/idl-4.2.pdf
(gitignored).
Werkzeug: tools/idl-4.2-gap-scan/scan.sh. Belege:
tools/idl-4.2-gap-scan/cases.tsv (357 Proben) und
tools/idl-4.2-gap-scan/expected.tsv (der aufgezeichnete Stand).
Die übrigen Seiten unter docs/spec-coverage/ sind Positivlisten: sie
nennen, was betrachtet wurde, und wie weit es kam. Was nie betrachtet
wurde, kann in ihnen nicht stehen, weil ihr Inhalt aus dem Baum stammt
und nicht aus der Spezifikation. Diese Seite ist die andere Richtung. Ihr
Nenner ist Annex A der Spezifikation — alle 227 Grammatikproduktionen —
plus die 24 standardisierten Annotationen aus Klausel 8 und die
normativen Klauseln aus 7.2 (Lexik), 7.3 (Präprozessor), 7.4.13/7.4.15
(Semantik von Bit Sets, Bit Masks und Annotationsanwendung) und 7.5
(Namen und Sichtbarkeit).
Jede Zeile hier hat einen Versuch hinter sich: die IDL, die gefüttert wurde, und was zurückkam. Keine Zeile steht auf einer Vermutung.
Wie gemessen wurde
Pro Konstrukt eine minimale .idl-Datei, durch alle sechs Unterbefehle
von zerodds-idlc. Ein Konstrukt kann in jedem einzeln fehlen, und die
erste Fassung dieser Erhebung hat nur check und generate gemessen —
die anderen vier standen als Annahme da. Das ist dieselbe Art
Positivliste, gegen die diese Seite angetreten ist, und deshalb ist die
Messung jetzt eine Matrix Probe × Unterbefehl:
| Spalte | Kommando | Frage |
|---|---|---|
check |
zerodds-idlc check |
erkennt das Frontend das Konstrukt |
strict |
zerodds-idlc check --strict |
ändert --strict daran etwas |
lint |
zerodds-idlc lint |
kommt es durch die Regeln, die eine gültige Datei noch brechen kann |
format |
zerodds-idlc format |
gibt es eine kanonische Form |
trip |
format, neu parsen, vergleichen |
hält der Round-Trip |
dump |
zerodds-idlc dump-ast |
zeigt das Modell das Konstrukt |
deps |
zerodds-idlc print-deps |
trägt der Abhängigkeitslauf es |
rust |
zerodds-idlc generate --lang rust |
gibt es eine Emission |
c |
zerodds-idlc generate --lang c |
dasselbe im zweiten Backend |
trip ist die Spalte gegen den stillen Verlust. Ein Konstrukt, das der
Parser kennt und der Formatierer nicht, verschwindet beim Reformatieren:
keine Fehlermeldung, nur eine Datei, aus der etwas fehlt. Ein Vergleich
der beiden formatierten Texte fängt das nicht — der zweite format eines
Textes, der das Konstrukt schon verloren hat, reproduziert den Verlust.
Verglichen wird deshalb das Modell: dump-ast und print-deps der
Quelle gegen dump-ast und print-deps der formatierten Ausgabe, plus
Idempotenz des Formats. - steht, wo es nichts zu vergleichen gibt, weil
format oder check die Datei abgewiesen hat.
Dazu eine Messung, die nicht am Werkzeug hängt: fixtures zählt, wie
viele der 748 committeten *.idl-Dateien im Baum das Konstrukt
überhaupt enthalten, per Textmuster (Spalte witness in cases.tsv).
Das ist schwächer als „es gibt einen Test dafür” — eine Null ist
trotzdem eine Tatsache: keine IDL im Baum trägt das Konstrukt, also kann
nichts im Baum es ausgeübt haben. Ein - heißt, dass kein Textmuster das
Konstrukt isoliert; dort ist die Fixture-Frage mit dieser Methode nicht
messbar und steht als ungeprüft.
Die Zahl
357 Proben. Die Befunde verteilen sich so — F überschneidet sich mit den
übrigen Kategorien, weil dieselbe Probe in check einen Befund haben kann
und in format einen anderen:
| Kategorie | Proben | |
|---|---|---|
| A | nicht erkannt — das Frontend weist ab | 14 → 13 → 0 |
| B | akzeptiert, obwohl die Spezifikation eine Zurückweisung verlangt | 19 → 2 |
| C | erkannt, nicht emittiert | 12 → 75 → 74 |
| D | emittiert, aber kein .idl im Baum enthält es |
33 → 26 → 0 |
| E | --strict ohne Wirkung |
4 → 0 |
| F | format nimmt an, was check abweist |
21 → 0 |
| G | format weist ab, was check annimmt |
3 → 0 |
A ist leer: die drei Grammatikformen aus A.1 bis A.3 und die acht
Präprozessorformen aus A.5 gehen durch check. C ist um 63 Proben
gewachsen, ohne dass etwas übersehen wurde: die C-Spalte lehnt jede
interface ab, seit fbbed5a2230 eine Interface-Bindung verweigert, was
sie nicht vollständig abbilden kann, und die Rust-Spalte lehnt den
Objekttyp ab, den ein Port oder Konnektor trägt (§C unten); die Union
ohne default: ist aus C heraus (§C). G kam aus A.5 und ist leer:
format prüft die Datei wie check nach dem Präprozessor (§G).
Auf Produktionsebene, gezählt über die Proben der Kategorie grammar in
cases.tsv (161 Produktionen tragen eine Probe): 73 haben einen Befund —
keine eine zurückgewiesene Form, 64 eine nicht emittierte, 14 eine ohne
Fixture (mit Überschneidungen). Vor der Neuaufzeichnung waren es nach
derselben Zählung 27 (3, 12, 14). Die Zahlen früherer Fassungen dieser
Seite (227 Produktionen, 37 mit Befund) zählten anders abgegrenzt und
sind mit diesen nicht vergleichbar.
Kategorie B ist keine der vier Kategorien aus dem Auftrag. Sie musste dazu, weil das Frontend nicht nur übersehen kann, was die Spezifikation erlaubt, sondern auch, was sie verbietet — und im zweiten Fall entsteht keine Fehlermeldung, sondern ein Modell mit falschem Inhalt. Drei der 19 Zeilen sind belegt genau das: der deklarierte Typ einer Konstanten verschwindet und wird durch den Typ des Literals ersetzt.
Die Matrix
Der Stand je Unterbefehl über alle 357 Proben. Pro Probe steht er in
tools/idl-4.2-gap-scan/expected.tsv, eine Zeile je Probe, eine Spalte
je Unterbefehl — das ist der Beleg, den diese Seite zitiert.
| Spalte | ok |
err |
- |
|---|---|---|---|
check |
324 | 33 | — |
strict |
321 | 36 | — |
lint |
324 | 33 | — |
format |
321 | 36 | — |
trip |
321 | — | 36 |
dump |
324 | 33 | — |
deps |
324 | 33 | — |
rust |
307 | 50 | — |
c |
249 | 108 | — |
check, lint, dump und deps stimmen Probe für Probe überein: alle
vier gehen über analyse(). format steht drei Proben darunter, das ist
G. strict steht drei Proben darunter — die drei Vendordirektiven aus E.
trip ist überall dort -, wo check oder format die Datei
abgewiesen hat, und ok sonst: kein stiller Verlust im Formatierer.
rust und c stehen darunter, und das ist Kategorie C: 50
beziehungsweise 108 Proben, die das Frontend erkennt und ein Backend
bewusst nicht emittiert.
Die Fixture-Zahlen in den Tabellen dieser Seite sind vom Stand vor der
Neuaufzeichnung, gezählt über 748 Dateien; expected.tsv trägt sie über
die 821 Dateien, die der Baum jetzt hat.
A — nicht erkannt
Die Spezifikation erlaubt es, zerodds-idlc check weist es ab.
A.1 (17) <literal> — benachbarte String-Literale (geschlossen)
Jetzt: ok in allen neun Spalten (811b31a6685). Beleg:
crates/idl-lower/tests/idl_4_2_grammar.rs. Der Befund, wie er stand:
Spec: §7.2.6.3 — „Adjacent string literals are concatenated. Characters in concatenated strings are kept distinct.”
IDL: const string K = "ab" "cd";
Ergebnis: error: [IDL0002] expected ';' after the constant (1:23),
gefolgt von [IDL0001] expected a declaration.
Fixtures: 0 von 748.
Probe: bb1-017-lit-string-concat
A.2 (64) <type_declarator> — <constr_type_dcl> in einem typedef (geschlossen)
Jetzt: ok in allen neun Spalten (811b31a6685). Beleg:
crates/idl-lower/tests/idl_4_2_grammar.rs. Der Befund, wie er stand:
Spec: §7.4.1.4.4.7 „Naming Data Types”, Produktion 64 —
<type_declarator> ::= { <simple_type_spec> | <template_type_spec> | <constr_type_dcl> } <any_declarators>.
Die dritte Alternative ist die Definition eines Struct, Union oder Enum
direkt im typedef.
IDL: typedef struct Inline { long m; } Ali;
Ergebnis: error: [IDL0001] expected a type (1:9), drei Folgefehler.
Fixtures: 0 von 748.
Probe: bb1-064-typedef-constr
A.3 (112) <export> ::+ … — typeid, typeprefix und import im Interface-Body (geschlossen)
Jetzt: check bis deps und rust ok (811b31a6685); c weist
ab wie jedes interface (Kategorie C). Beleg:
crates/idl-lower/tests/idl_4_2_grammar.rs. Der Befund, wie er stand:
Spec: §7.4.6.3, Produktion 112 —
<export> ::+ <type_id_dcl> ";" | <type_prefix_dcl> ";" | <import_dcl> ";" | <op_oneway_dcl>.
Auf Dateiebene funktionieren alle drei (Proben bb6-113-typeid,
bb6-114-typeprefix, bb6-115-import-scoped, alle ok). Nur die
Erweiterung von <export> fehlt, also die Form im Rumpf eines Interface.
IDL / Ergebnis:
| IDL | erste Fehlermeldung |
|---|---|
interface Ia { typeid Ia "IDL:Ia:2.0"; }; |
[IDL0001] expected a type (1:16) |
interface Ia { typeprefix Ia "ex.org"; }; |
[IDL0001] expected a type (1:16) |
interface Ia { import Mb; }; |
[IDL0001] expected a type (2:19) |
Fixtures: nicht per Textsuche messbar.
Proben: bb6-112-export-typeid, bb6-112-export-typeprefix,
bb6-112-export-import
A.4 §7.4.15.4.2 — unbekannte Annotation mit Bezeichner als Argument (geschlossen)
Spec: §7.4.15.4.2 — „in case an unknown annotation is encountered, it should be ignored by the IDL-processing tools.”
Zwei der drei Formen wurden ignoriert, wie die Klausel es verlangt. Die dritte nicht:
| IDL | vorher | jetzt |
|---|---|---|
@nosuchthing struct Sa { long m; }; |
0 Fehler | 0 Fehler |
@nosuchthing(v = 1) struct Sa { long m; }; |
0 Fehler | 0 Fehler |
@nosuchthing(v = SOME_NAME) struct Sa { long m; }; |
[IDL0010] a name does not resolve: … ["SOME_NAME"] |
0 Fehler |
Ein Bezeichner als Argumentwert wurde aufgelöst, obwohl die Annotation,
zu der er gehört, ignoriert werden soll. Damit scheiterte jede Datei mit
@RTI_topic_kind(NO_KEY) oder @allowed_data_representation(XCDR2) —
die Form, in der Vendorannotationen ihre Enum-Werte schreiben. Das ist
kein Erhebungsfall: es ist die IDL, die Anwender vorlegen.
Der Fix. Lowering::annotation in
crates/idl-lower/src/annotation.rs stellt fest, ob die Annotation
bekannt ist — BUILTIN_ANNOTATIONS oder im Scope als AnnotationDef
gebunden. Ist sie es nicht, wird ein Argument, dessen Wert ein Bezeichner
ist, nicht als Ausdruck gelowert. Der Mechanismus existierte bereits für
die enumerierten Parameter der Builtins (@autoid(HASH),
@extensibility(FINAL)) und trägt jetzt beide Fälle: der Bezeichner
gehört zum Typ der Annotation und steht in keinem Scope der Datei.
Ein Literal wird weiter gelowert — @nosuchthing(v = 1) behält seine
1. Nur ein Bezeichner hat nichts, worauf er in dieser Datei zeigen
könnte.
Ein zweiter Befund derselben Familie, mit gefixt. @verbatim fehlte
in der Liste der Builtins mit enumeriertem Parameter, obwohl IDL 4.2
§8.3.5.1 PlacementKind placement deklariert. Jedes
@verbatim(placement = BEFORE_DECLARATION) im Baum wurde über den
Enumerator abgewiesen. verbatim_annotation liest ohnehin alle drei
Parameter aus Argument::text und Argument::identifier, also geht
nichts verloren.
crates/idl-parse/tests/fixtures/spec_features/rti_connext_pragmas.idl
trug beide Defekte in einer Datei und geht jetzt durch check.
Tests: tools/idlc/tests/annotation_arguments.rs — sechs Tests auf
Compose-Ebene, weil die Diagnose aus der Resolve-Stufe kam. Die erste
Fassung stand in idl-lower und wäre mit und ohne Fix grün gewesen; eine
Mutation hat das gezeigt.
Mutation: die Bedingung abschalten (3 rot), Literale mit verwerfen
(1 rot), verbatim aus der Liste nehmen (2 rot), eine deklarierte
Annotation als unbekannt behandeln (1 rot).
Probe: sem-7.4.15-unknown-annotation-enum-arg — jetzt ok.
A.5 §7.3 — der Präprozessor ist nicht der aus ISO/IEC 14882:2003 (geschlossen)
Jetzt: alle acht Proben ok in check, lint, dump, deps,
rust und c (9303a2a33cf): funktionsartige Makros, #, ## mit
Platzhaltern, der Rescan mit Hide-Sets, #if über einen vollständigen
Konstantenausdruck, __LINE__, __FILE__, Zeilenfortsetzung. Neuer Code
IDL0022. Beleg: crates/idl-parse/tests/preprocessor_iso_14882.rs.
format weist drei davon ab, siehe G. Der Befund, wie er stand:
Spec: §7.3 — „IDL shall be preprocessed according to the specification of the preprocessor in ISO/IEC 14882:2003.”
Sechs Formen dieses Präprozessors fehlen. Zwei davon melden sich selbst mit eigener Diagnose — das ist die bessere Hälfte des Befunds, weil ein stiller Ausfall an dieser Stelle Text verschluckt hätte.
| IDL | Ergebnis |
|---|---|
#define TWICE(x) ((x) * 2) + const long K = TWICE(3); |
[IDL0002] expected ';' after the constant (1:22) — funktionsartige Makros werden nicht expandiert |
#define CAT(a, b) a##b + struct CAT(S, a) { … }; |
[IDL0002] expected '{' opening the struct body (1:12) — ## |
#define STR(x) #x + const string K = STR(ab); |
[IDL0002] expected ';' after the constant (2:4) — # als Stringize-Operator |
#define NN \ + Fortsetzungszeile 4 |
[IDL0001] expected a constant expression (3:12) — Zeilenfortsetzung mit Backslash |
#define AA 1 + #define BB AA + const long K = BB; |
[IDL0006] macro 'BB' expands to 'AA', which is itself a macro; nested expansion is not performed |
#if 2 * 3 > 5 |
[IDL0006] '#if 2 * 3 > 5' is not a form this preprocessor evaluates |
const long K = __LINE__; |
[IDL0010] a name does not resolve: … ["_LINE__"] — vordefinierte Makros gibt es nicht |
const string K = __FILE__; |
[IDL0010] a name does not resolve: … ["_FILE__"] |
Was funktioniert und hier festgehalten wird, damit es das bleibt:
#include in beiden Formen, #define ohne Parameter, #undef,
#ifdef, #ifndef, #if/#else/#elif/#endif mit einer Konstanten,
defined(X), #line, #pragma, #pragma once.
Proben: lex-7.3-define-function, lex-7.3-token-paste,
lex-7.3-stringize, lex-7.3-line-continuation, lex-7.3-nested-macro,
lex-7.3-if-arithmetic, lex-7.3-predefined-line,
lex-7.3-predefined-file
B — akzeptiert, obwohl die Spezifikation eine Zurückweisung verlangt
Hier gibt es keine Fehlermeldung, sondern ein Ergebnis. In den ersten drei Zeilen war dieses Ergebnis nachweislich falsch; diese drei sind geschlossen.
B.1 §7.4.1.4.3 — der deklarierte Typ einer Konstanten (geschlossen)
Spec: §7.4.1.4.3 „Constants” — eine Konstantendeklaration besteht
unter anderem aus „a value expression (<const_expr>), which shall be
consistent with the type declared for the constant”. §7.2.6.3 —
„Attempts to assign a wide string literal to a non-wide string constant
or to assign a non-wide string literal to a wide string constant result
in a compile-time diagnostic.”
Der deklarierte Typ wurde verworfen; der Typ des Literals ersetzte ihn:
| IDL | check vorher |
erzeugtes Rust vorher | check jetzt |
|---|---|---|---|
const long K = "x"; |
0 Fehler | pub const K: &'static str = "x"; |
[IDL0019] |
const string Ss = L"x"; |
0 Fehler | pub const Ss: &'static str = "x"; |
[IDL0019] |
const wstring Ss = "x"; |
0 Fehler | pub const Ss: &'static str = "x"; |
[IDL0019] |
Ein Anwender, der const long schrieb und einen &str bekam, erfuhr es
vom Rust-Compiler an der Verwendungsstelle, nicht hier.
Der Fix. Lowering::check_const_value_form in
crates/idl-lower/src/lower.rs vergleicht die Form des Wertes mit dem
deklarierten Typ. Der Kommentar an der Stelle behauptete die Regel schon
— „the expression is checked against it rather than used to derive it” —
und es stand keine Prüfung darunter. Neuer Diagnosecode IDL0019 TypeMismatch; die Codes werden angehängt, nie umnummeriert.
Wo die Prüfung schweigt, und warum. Sie greift nur, wenn der
deklarierte Typ hier zu einem Primitive, string/wstring oder fixed
auflöst und der Ausdruck sich auf eine Literalform festlegt. Ein
benannter Typ, ein Alias, eine Enumeration, eine Referenz auf eine andere
Konstante und eine ungelöste Referenz gehen wortlos durch. Eine
Zurückweisung, die zu weit greift, bricht gültiges IDL — teurer als der
Fehler, den sie behebt. Gemessen: keine der 748 committeten
*.idl-Dateien meldet IDL0019.
Tests: crates/idl-lower/tests/validation.rs —
a_constant_whose_value_is_not_of_its_declared_type_is_refused,
the_wide_and_narrow_string_pair_is_the_case_the_clause_names und
every_form_the_specification_admits_still_gets_through (28 Formen, die
weiter durchgehen müssen). Mutation: if admitted || true macht die
ersten beiden rot, _ => (false, …) statt _ => return den dritten.
Proben: scope-7.4-const-type-mismatch,
scope-7.2.6.3-wide-to-narrow, scope-7.2.6.3-narrow-to-wide — alle
drei jetzt err in allen neun Spalten.
B.2 §7.5.2 — drei Sichtbarkeitsregeln (eine geschlossen, zwei bewusst offen)
Spec: §7.5.2 — „The name of a module, structure, union, map,
interface, value type, event type, exception or home may not be redefined
within the immediate scope of the module, …“. Die Spezifikation gibt
beide Fälle als Beispiel mit dem Kommentar // Error.
| IDL | Spec sagt | vorher | jetzt |
|---|---|---|---|
module Ma { typedef short Ma; }; |
Error (Beispiel §7.5.2) | 0 Fehler | [IDL0008] |
interface Ia { void ia(in short jj); }; |
Error (Beispiel §7.5.2) | 0 Fehler | 0 Fehler — offen |
module Inner2 { typedef Inner1::Ss1 Ss2; typedef string inner1; }; |
Error (Beispiel §7.5.2) | 0 Fehler | 0 Fehler — offen |
Die erste ist geschlossen. check_redefines_enclosing_scope in
crates/idl-lower/src/lower.rs, gerufen aus check_collision, vergleicht
den Namen einer Deklaration mit dem Namen des Scopes, in dem sie steht —
case-insensitiv, weil §7.2.3 jeden Bezeichnervergleich in IDL so macht.
Nur der unmittelbare Scope, und das ist die ganze Sicherheit der
Regel: module Ma { module Mb { typedef short Ma; }; }; bleibt zulässig,
weil Ma nicht der Name des Scopes Mb ist. Der Unterschied zwischen
dieser Regel und einer, die gültiges IDL bricht, liegt genau darin.
Die Regel bestätigt eine Entscheidung, die der Baum schon getroffen
hatte: Commit 4051da478d0 hielt fest, dass
module common { struct common { … } } kein zulässiges IDL ist, und
korrigierte Kommentare in den Java- und TypeScript-Adaptern, die das
Gegenteil behaupteten. Der Interop-Fall
tests/interop-corpus/resolve-module-named-as-type/type.idl misst, welche
Vendoren ihn abweisen; er steht jetzt auf der Ausnahmeliste des
Korpus-Tests.
Zwei bleiben offen, und zwar begründet.
- Die Operation, die mit dem Interface-Namen kollidiert. §7.5.2 zählt
für die Redefinitionsregel „module, structure, union, map, interface,
value type, event type, exception or home” auf — Operationen stehen
nicht darin; der Fall stammt aus dem Introduction-Beispiel. Ob die
Regel für jeden
<export>gilt oder nur für Operationen, ist Auslegung, und zu weit greifen bricht CORBA-Interfaces. - Die „identifier introduction”-Regel. Sie verlangt, jede
Namensbenutzung pro Scope case-insensitiv zu verfolgen. Das ist das
höchste Über-Reichweiten-Risiko der ganzen Liste. Die beiden
Gegenproben (
scope-7.5.2-not-introduced-ok,scope-7.5.2-rooted-no-introduce) gehen heute durch — richtig, aber aus dem falschen Grund, weil die Regel gar nicht existiert. Eine Umsetzung braucht deutlich mehr Gegenproben als diese zwei.
Was §7.5.2 sonst hält: Modul-Wiedereröffnung, Redefinition in einem
verschachtelten Scope, enum-Namen im umgebenden Scope
(scope-7.5.2-enum-name-in-scope wird korrekt abgewiesen), und die
Case-Kollisionsregel aus §7.2.3, die sogar Bezeichner gegen Keywords
prüft.
Tests: crates/idl-lower/tests/validation.rs —
a_declaration_may_not_reuse_the_name_of_its_own_scope und
only_the_immediate_scope_is_compared.
Mutation: die Bedingung abschalten (1 rot); den Scope-Namen durch den
Deklarationsnamen ersetzen, also jede Deklaration treffen (33 rot).
Proben: scope-7.5.2-redefine-own-name — jetzt err;
scope-7.5.2-op-clashes-iface und scope-7.5.2-introduced-ident bleiben
ok.
B.3 §7.4.1.4.4 — direkte Rekursion (geschlossen)
Spec: §7.4.1.4.4.4.4 — eine Struktur darf sich nur über eine Sequence
(oder ein @optional-Member, oder eine Map) selbst enthalten.
IDL: struct Sa { Sa m; };
Vorher: check meldete 0 Fehler. generate meldete
SpecRefuses { detail: "NodeId(3) closes a type cycle with nothing on it that terminates a value — no sequence, no map and no @optional member — which IDL 4.2 §7.4.1.4.4.4.4 does not admit; …" }.
Die Regel war also da und zitierte die Klausel; sie saß nur in der
Layout-Stufe statt im Frontend. Wer check benutzte, um zu erfahren, ob
eine Datei gültiges IDL ist, erfuhr es nicht.
Der Fix — herausgelöst, nicht nachgebaut.
zerodds_ast_layout::types_that_close_an_inadmissible_cycle fährt
denselben plan-Lauf und behält genau diesen einen Fehler. Es ist
ausdrücklich keine zweite Implementierung des Zyklustests: die
Barrieren-Buchführung — ein Zyklus ist genau dann zulässig, wenn eine
Sequence, eine Map oder ein @optional-Member auf ihm liegt — lebt im
Abstieg, und eine zweimal geschriebene Regel driftet. Jeder andere
LayoutError wird verworfen; eine ungelöste Referenz meldet die
Resolve-Stufe, und eine Kodierung, die einen Typ nicht trägt, ist
generates Sache.
Aufgerufen wird sie in zerodds_idl_compose::compose_source_with, nach
der Resolve-Stufe (ein Zyklus ist eine Eigenschaft des aufgelösten
Typgraphen). Damit bekommt jeder Aufrufer des Frontends dieselbe Antwort
— check, ein build.rs über zerodds-build, eine Wire-Probe — statt
nur die CLI. Das Verhalten von ast-layout ist unverändert.
Die Meldung nennt den Typ mit vollem Namen. Die der Layout-Stufe nannte
eine NodeId, was außerhalb eines Debuggers schlechter ist als nichts.
Zulässige Rekursion bleibt zulässig, und das ist der teurere Teil der
Regel: sequence<Sa>, sequence<Sa, 4>, @optional Sa, map<long, Sa>
und gegenseitige Rekursion über Sequences gehen weiter durch. Der
Interop-Korpus hält sieben Rekursionsfälle; genau einer —
recursive-array — ist die unzulässige Form, und der Test liest alle
sieben von der Platte statt sie nachzuschreiben.
Tests: crates/idl-compose/tests/recursion.rs, vier Tests.
Mutation: den Aufruf auskommentieren (3 rot), im Extraktor den falschen
LayoutError behalten (3 rot).
Probe: scope-7.4-recursive-struct — jetzt err.
B.4 §7.4.13 — sieben Schranken an Bit Sets und Bit Masks (geschlossen)
Spec: §7.4.13.4.3.2 — „The first one (<positive_int_const>) is the
number of bits that can be stored (its size). The maximum value is 64”;
„When the destination type is given, the number of stored bits cannot
exceed its size (i.e., 1 for boolean, 8 for octet, …)“. §7.4.13.4.3.3 —
„A bit mask must contain at least one value and no more than its size
expressed in bits”; „By default, the size of a bit mask is 32”;
@bit_bound „must be lower than or equal to 64. Accordingly the number
of values cannot then exceed the value given to @bit_bound“;
@position „Possible positions range from 0 … up to (size – 1)”.
| IDL | vorher | jetzt |
|---|---|---|
bitset Ba { bitfield<65> f1; }; |
check 0 Fehler; erst der C-Writer verweigert: NoWriter { … "bitset wider than the sixty-four bits an anchor holds" } |
[IDL0014] |
bitset Ba { bitfield<9, octet> f1; }; |
0 Fehler, Emission in beiden Sprachen | [IDL0014] |
bitset Ba { bitfield<2, boolean> f1; }; |
0 Fehler, Emission in beiden Sprachen | [IDL0014] |
@bit_bound(65) bitmask Ba { F1 }; |
0 Fehler | [IDL0014] |
@bit_bound(2) bitmask Ba { F1, F2, F3 }; |
0 Fehler | [IDL0014] |
bitmask Ba { F01 … F33 }; (33 Werte, Default-Größe 32) |
0 Fehler | [IDL0014] |
@bit_bound(2) bitmask Ba { @position(5) F1 }; |
0 Fehler | [IDL0014] |
Die eine Schranke, die das Frontend schon hielt, ist die untere:
bitmask Ba { }; wird mit [IDL0002] a 'bitmask' needs at least one bit value abgewiesen.
Der Fix. Lowering::check_bit_bounds in
crates/idl-lower/src/complete.rs, aus complete() gerufen und nicht
aus der Deklaration: @bit_bound und @position sind Annotationen und
sind erst angewandt, wenn die Datei fertig gelowert ist. Diagnosecode
IDL0014 InvalidBound, der vorhandene Code für eine Schranke außerhalb
ihres zulässigen Bereichs.
Die Zielbreiten sind die, die §7.4.13.4.3.2 nennt: 1 für boolean, 8 für
octet/int8/uint8, 16, 32, 64. Ein Zieltyp, der hier nicht auflöst
oder keiner dieser Typen ist, führt zu keiner Meldung — dieselbe Regel
wie in B.1: schweigen, wo die Antwort nicht feststeht.
Eine Datei im Baum trifft es.
tools/wire-oracle/cases/bitmask_past_bound/type.idl schreibt
@bit_bound(4) mit @position(7), absichtlich: der Fall misst, was
fremde Generatoren mit einer Position außerhalb des Bereichs tun. Er wird
von tools/byte-gate/gate.sh nur an Cyclone, Fast DDS, OpenDDS und RTI
gegeben — die ZeroDDS-Seite kompiliert ihn nicht —, also bleibt die
Messung, die er trägt, unverändert. Kein Test im Baum fährt check über
alle committeten .idl-Dateien.
Tests: crates/idl-lower/tests/validation.rs — fünf Tests, einer je
Regel, plus the_bit_forms_the_specification_admits_still_get_through
mit neun Formen, die weiter durchgehen müssen.
Mutation: if false && … vor jeder der vier Vergleiche macht je einen
Test rot; MAX_BITFIELD_BITS/MAX_BIT_BOUND von 64 auf 63 macht drei
rot.
Proben: sem-7.4.13-bitfield-over-64,
sem-7.4.13-bitfield-dest-octet, sem-7.4.13-bitfield-dest-bool,
sem-7.4.13-bitbound-over-64, sem-7.4.13-bitmask-over-bound,
sem-7.4.13-bitmask-over-default, sem-7.4.13-position-out-of-range —
alle sieben jetzt err.
B.5 §7.4.15.4.2 — vier Regeln zur Annotationsanwendung (geschlossen)
Spec: §7.4.15.4.2 — „Members with no default value must be given a value”; „A constant expression, whose type must be compatible with the member’s declaration”; „In case there is only one member, the annotation application may be shortened to the name of the annotation prefixed with @ and followed with the constant value of that unique member”; und schließlich „malformed definitions shall be treated as an error”.
| IDL | vorher | jetzt |
|---|---|---|
@annotation Aa { long v; }; + @Aa struct … — Pflichtmember ohne Wert |
0 Fehler | [IDL0007] |
@Aa("x") auf long v default 0 — Typ passt nicht |
0 Fehler | [IDL0007] |
@Aa(nosuch = 1) — Membername existiert nicht |
0 Fehler | [IDL0007] |
@Aa(1) auf eine Annotation mit zwei Membern |
0 Fehler | [IDL0007] |
Der Defekt darunter war größer als die vier Regeln. Keine der vier
konnte greifen, weil eine Annotation ihre Member gar nicht im Modell
hatte. Regel (222) schreibt den Namen eines Annotationsmembers als
<simple_declarator>, und parse_annotation_decl liest ihn mit einem
blanken expect(Ident) ohne Declarator-Knoten. Lowering::declared
läuft über Declarator-Kinder, fand keine, gab eine leere Liste zurück —
und jeder Member jeder Annotation verschwand ohne ein Wort.
@annotation Aa { long v; }; erreichte das Modell mit null Membern.
annotation_decl liest den Namen jetzt direkt (first_ident liest die
eigenen Tokens des Knotens, nicht die seiner Nachfahren, also ist der
Name von Ea v; gleich v und nicht Ea) und meldet einen Member ohne
Namen, statt ihn fallen zu lassen.
Und der Rumpf ist ein eigener Scope. Regel (221) lässt neben den
Membern ein enum, ein const und ein typedef zu —
@annotation Mode { enum Kind { READ, WRITE }; Kind kind default READ; };.
Die wurden geparst und nie gelowert, was niemandem auffiel, solange die
Member selbst verschwanden. Mit gelesenen Membern hatte der Typ eines
Members nichts, worauf er auflösen konnte:
crates/idl-parse/tests/fixtures/spec_features/user_annotations.idl fiel
sofort um. annotation_decl schiebt jetzt einen Scope und lowert die
drei zulässigen Formen darin.
Die Grenze zur Ignorier-Regel ist die der Klausel selbst. Dieselbe
§7.4.15.4.2 verlangt, eine unbekannte Annotation zu ignorieren. Keine
der vier Regeln erreicht deshalb eine Annotation, die die Datei nicht
deklariert hat: check_annotation_application wird nur mit einer
aufgelösten Deklaration gerufen. @RTI_topic_kind(NO_KEY) bleibt
unberührt.
Die Typprüfung ist dieselbe wie in B.1.
Lowering::value_disagrees_with beantwortet beide Klauseln — §7.4.1.4.3
für den Wert einer Konstanten und §7.4.15.4.2 für ein Annotationsargument.
Eine Lesart, damit die beiden nicht auseinanderlaufen.
Offen geblieben, weil nichts es entscheidet: ob ein Enumerator, der
im Rumpf einer Annotation deklariert ist, an einer Anwendungsstelle
unqualifiziert geschrieben werden darf — @Mode(WRITE). Weder §7.5 noch
Regel (221) sagen es, und keine Fixture im Baum übt es. Es steht deshalb
keine Behauptung darüber im Test.
Tests: crates/idl-lower/tests/validation.rs — fünf Tests, einer je
Regel plus none_of_the_four_rules_reaches_an_unknown_annotation und
an_annotations_declared_members_reach_the_model;
tools/idlc/tests/annotation_arguments.rs für den Rumpf-Scope.
Mutation: jede der vier Bedingungen einzeln abschalten (je 1 rot), den
Aufruf ganz entfernen (4 rot).
Proben: sem-7.4.15-missing-required,
sem-7.4.15-param-type-mismatch, sem-7.4.15-unknown-param-name,
sem-7.4.15-shorthand-two-members — alle vier jetzt err.
sem-7.4.15-unknown-annotation und -unknown-annotation-args bleiben
ok; sie sind die Gegenproben und kein Befund.
B.6 §7.3 — #error (geschlossen)
Spec: IDL 4.2 §7.3 übernimmt den Präprozessor aus ISO/IEC 14882:2003. Dessen §16.5: „#error causes the implementation to produce a diagnostic message that includes the specified sequence of preprocessing tokens.”
IDL: #error stop here
Vorher: check meldete 0 Fehler; generate --lang rust schrieb eine
Datei ohne Inhalt. Die Direktive wurde still übergangen — die eine
Direktive, die es gibt, um einen Build anzuhalten, war die einzige, die
das nicht konnte.
Jetzt: [IDL0020] mit den Tokens in der Meldung, wie die Klausel es
verlangt. Neuer Diagnosecode, angehängt; IDL0019 bleibt TypeMismatch,
weil Codes nie umnummeriert werden.
Die einzige Feinheit der Regel ist if keeping: ein #error in
einem Zweig, den die Konditionale herausgeschnitten hat, ist Text, der
nie erreicht wurde. Ihn zu melden würde jede Datei abweisen, die eines
absichert — also fast jede, die überhaupt eines hat.
Tests: crates/idl-parse/tests/preprocess.rs — drei Tests, darunter
an_error_directive_in_a_branch_that_is_cut_out_says_nothing über
#ifdef, #else und #if 0.
Mutation: die Direktive nicht melden (2 rot); if keeping entfernen, also
auch im toten Zweig melden (1 rot).
Probe: lex-7.3-error-directive — jetzt err.
C — erkannt, nicht emittiert
Alle elf Zeilen sind bewusst abgelehnt: das Backend nennt in jedem Fall den Grund und, wo es einen gibt, die Klausel. Das ist keine Lücke im Sinne von „übersehen”, muss aber unterscheidbar sein, weil eine Anwendung, die das Konstrukt benutzt, nichts bekommt.
| Konstrukt | Produktion | Rust | C | Grund, wie ihn das Werkzeug nennt | Fixtures |
|---|---|---|---|---|---|
any als Member |
69, 70 | — | — | NoLanguageForm { construct: "any" }, in C zusätzlich NoWriter { … "'any', which DDS-XTypes has no encoding for; only CORBA CDR writes one, as a TypeCode ahead of the value" } |
9 |
typedef any |
70 | — | — | dito | 1 |
any als Annotationsmember |
223, 224 | ok | — | wie oben | – |
Object |
117, 118 | — | — | NoLanguageForm { construct: "Object" } |
4 |
ValueBase |
131, 132 | — | — | NoLanguageForm { construct: "ValueBase" } |
1 |
fixed<9,2> |
42 | — | ok | NoWriter { member: "m", kind: "fixed" } |
17 |
valuetype Va string; (Value Box) |
125, 126 | ok | — | NoWriter { … "boxed value, whose presence flag has no settled wire form" } |
1 |
abstract valuetype |
125, 127 | ok | — | NoLanguageForm { … "an aggregate with no members — C23 §6.7.2.1p8 leaves a structure with no named member undefined" } |
0 |
abstract eventtype (Definition) |
166, 168 | ok | — | dito | – |
abstract eventtype (Forward + Definition) |
167 | ok | — | dito | 1 |
struct Sa { }; (leerer Struct, BB Extended Data-Types) |
195 | ok | — | dito | 3 |
Geschlossen: die Union ohne default:, deren Default-Diskriminator
keinen Zweig trifft (Probe bb1-050-union-no-default, Produktionen 49,
50), ist in der Rust-Spalte ok. Das enum der Union hat die Variante
_NoMember(<Diskriminator>) für einen Diskriminator, der kein Member
wählt (IDL 4.2 §7.4.1.4.4.4), und eine Variante, deren Fall den
Diskriminator nicht festlegt (mehrere Labels, default:), trägt ihn vor
dem Member. Test:
crates/lang-rules/tests/rust_corpus_compiles.rs::a_union_keeps_its_discriminator_through_rustc_and_back.
Die letzten vier hängen an einer einzigen Ursache: C hat keine Form für
einen Aggregat-Typ ohne Member, und abstract valuetype,
abstract eventtype und der leere Struct aus Produktion 195 laufen alle
darauf zu. Rust emittiert alle vier.
Die 17 Fixtures mit fixed<…> und die 9 mit any sind der Hinweis, dass
diese beiden Konstrukte im Baum vorkommen, aber nicht durch die
Rust-Emission gehen.
C.2 Die Interface-Familie in C, der Objekttyp an Ports und Konnektoren in Rust
63 weitere Proben, alle mit Begründung abgelehnt:
- C, 62 Proben — jede Probe mit einem
interface, auch einer, die nur an einer Komponente, einem Home oder einem Porttyp hängt: „C has no complete DDS-RPC 1.0 binding for an interface, and this model declares 1 …“, mit der Liste der Spalten, die es können. Seitfbbed5a2230verweigert eine Interface-Bindung, was sie nicht vollständig abbilden kann, statt einen Teil zu erzeugen. Proben:bb3-074bisbb3-095,bb4-097-export-*,bb5-103-value-supports,bb6-113-typeid,bb6-114-typeprefix,bb6-119-local-interface,bb6-120-oneway,bb6-123-op-with-context,bb7-129,bb7-130,bb8-135bisbb8-143,bb9-145bisbb9-150,bb10-155bisbb10-165,bb11-173bisbb11-182,ann-8.3.6.1-service*,ann-8.3.6.2-oneway,ann-8.3.6.3-ami*,scope-7.5.2-op-clashes-iface,scope-7.5.3-type-name-lookup. - Rust, 12 Proben — ein Port (
provides,uses,port,mirrorport), ein Porttyp oder Konnektor hält eine Objektreferenz, und die Rust-Spalte hat fürObjectkeine Form:NoLanguageForm { construct: "Object", language: "Rust" }. Proben:bb8-141-provides,bb8-143-uses,bb10-157-interface-type-obj,bb10-158-uses-multiple,bb11-173bisbb11-182.
Nicht bestimmt: welcher Commit nach dem letzten Aufzeichnen die zwölf
Rust-Proben von ok auf err gebracht hat; die Ablehnung von Object
selbst besteht länger.
G — format weist ab, was check annimmt
zerodds-idlc format liest den Tokenstrom vor dem Präprozessor, damit
Kommentare und Direktiven erhalten bleiben. Ein objektartiges Makro steht
dort als Bezeichner und ist gültige Syntax; der Aufruf eines
funktionsartigen Makros nicht. format weist die Datei deshalb ab —
mit Diagnose, ohne etwas zu verlieren —, während check sie nach der
Expansion annimmt.
| IDL | format |
|---|---|
#define TWICE(x) ((x) * 2) + const long K = TWICE(3); |
[IDL0002] expected ';' after the constant |
Proben: lex-7.3-define-function, lex-7.3-token-paste,
lex-7.3-stringize.
Geschlossen: zerodds-idlc format entscheidet die Gültigkeit wie
check, nach dem Präprozessor, und legt den Text danach über die Tokens
der Quelle aus (format::canonical_of_checked); ein Makroaufruf bleibt
die Tokenfolge, als die er geschrieben ist. Die drei Proben sind in
format und im Rundlauf trip jetzt ok. Test:
tools/idlc/src/format.rs::tests::a_function_like_macro_call_keeps_its_tokens.
D — emittiert, aber kein .idl im Baum enthält es (geschlossen)
Beide Backends erzeugen Quelltext, und keine der committeten
*.idl-Dateien trug das Konstrukt. Nach der Neuaufzeichnung waren es
26. Jetzt hat jedes davon eine Fixture unter
tools/idlc/tests/idl-4.2-gaps/, eine je Probe und nach ihr benannt, und
tools/idlc/tests/idl_4_2_gaps.rs fährt sie durch zerodds-idlc check
und zerodds-idlc generate --lang rust und --lang c. Die Fixtures
tragen die IDL der Probe, damit der Text die Fixture-Frage beantwortet,
die scan.sh mit der witness-Spalte stellt.
Zuvor herausgefallen waren elf: @range, @min, @max, @unit,
const octet und union Ua; hatten eine Fixture bekommen; @ami,
interface Ia;, interface Ia : Ib, local interface und context
emittiert die C-Spalte nicht mehr (C.2). Vier waren hinzugekommen, weil
sie vorher nicht durch check kamen: benachbarte String-Literale,
typedef struct … Ali;, das funktionsartige Makro und ##.
Die 26, die die Fixture verlassen haben:
| Konstrukt | Produktion / Klausel | Probe |
|---|---|---|
const fixed |
6, 43 | bb1-006-const-fixed |
const wchar |
6 | bb1-006-const-wchar |
const wstring |
6 | bb1-006-const-wstring |
^ im konstanten Ausdruck |
9 | bb1-009-xor-expr |
>> im konstanten Ausdruck |
11 | bb1-011-shift-right |
~ als unärer Operator |
15 | bb1-015-unary-tilde |
| Fixed-Point-Literal | 17 | bb1-017-lit-fixed |
| Wide-String-Literal | 17 | bb1-017-lit-wstring |
| benachbarte String-Literale | 17 | bb1-017-lit-string-concat |
typedef struct … Ali; |
64 | bb1-064-typedef-constr |
valuetype Va; (Forward) |
99, 110 | bb5-110-value-forward |
int64 |
212 | bb13-212-int64 |
| oktaler Zeichen-Escape | 7.2.6.2 | lex-7.2.6.2-char-escape-octal |
| hexadezimaler Zeichen-Escape | 7.2.6.2 | lex-7.2.6.2-char-escape-hex |
| Unicode-Escape | 7.2.6.2 | lex-7.2.6.2-wchar-escape-unicode |
| Gleitkomma mit führendem Punkt | 7.2.6.4 | lex-7.2.6.4-float-leading-dot |
| Gleitkomma mit nachgestelltem Punkt | 7.2.6.4 | lex-7.2.6.4-float-trailing-dot |
| Fixed-Point-Literal | 7.2.6.5 | lex-7.2.6.5-fixed-literal |
| funktionsartiges Makro | 7.3 | lex-7.3-define-function |
#ifndef |
7.3 | lex-7.3-ifndef |
#if/#else |
7.3 | lex-7.3-if-else |
#elif |
7.3 | lex-7.3-elif |
defined(X) |
7.3 | lex-7.3-defined-operator |
#undef |
7.3 | lex-7.3-undef |
## |
7.3 | lex-7.3-token-paste |
#line |
7.3 | lex-7.3-line-directive |
Drei davon waren mit der witness-Suche vorher nicht messbar: die
Muster der beiden Zeichen-Escapes und des Unicode-Escapes in
cases.tsv waren über-escaped ('\\\\[0-7], '\\\\x, \\\\u…) und
trafen nicht einmal die IDL der eigenen Probe. Sie stehen jetzt als
'\\[0-7], '\\x und \\u[0-9a-fA-F] im Korpus.
E — --strict (geschlossen)
tools/idlc/README.md und zerodds-idlc --help beschreiben --strict
als „OMG IDL 4.2 only; reject every vendor extension”. Der Schalter wurde
geparst und dann verworfen — tools/idlc/src/main.rs, analyse():
let _ = options.strict; — und Parser::over() verdrahtete
Dialect::ALL_VENDORS fest. Über alle Proben gab es keine einzige, bei
der check und check --strict verschiedene Ergebnisse lieferten.
Der Weg, den der Dialekt jetzt nimmt. Options::dialect() in
tools/idlc/src/main.rs ist die eine Stelle, an der --strict gelesen
wird; jeder der sechs Unterbefehle geht durch sie — fünf über
analyse(), format über format::canonical_under. Der Dialekt
erreicht beide Stufen, in denen Vendorkonstrukte sitzen:
zerodds_idl_parse::parse_preprocessed_with für die Grammatik und
zerodds_idl_lower::Options::dialect für die Pragmas.
Gemessen, nach dem Fix:
| IDL | Vendor | check |
check --strict |
|---|---|---|---|
keylist Sa (m); |
RTI Connext | 0 Fehler, 1 Warnung | 1 Fehler |
#pragma keylist Sa m |
Cyclone DDS | 0 Fehler, 1 Warnung | 1 Fehler |
#pragma DCPS_DATA_TYPE "Sa" |
OpenDDS | 0 Fehler, 1 Warnung | 1 Fehler |
#pragma DCPS_DATA_KEY Sa m |
OpenDDS | 0 Fehler, 1 Warnung | 1 Fehler |
@Key (Fast-DDS-Schreibweise) |
— | 0 Fehler | 0 Fehler |
Die drei Direktiven sind die, auf die das Frontend handelt: alle drei
machen Member zu Keys, also baut ein Frontend, das sie ignoriert, aus
derselben Datei einen anderen Typ. Genau das ist die Frage, die
--strict beantwortet.
@Key bleibt zulässig, und das ist kein Rest der Lücke. IDL 4.2
§7.4.15.4.2: „in case an unknown annotation is encountered, it should be
ignored by the IDL-processing tools.” crates/idl-lower/src/annotation.rs
erkennt Annotationen case-sensitiv, @Key ist deshalb keine bekannte
Annotation, das Frontend handelt nicht darauf, und eine Zurückweisung
unter --strict bräche gültiges IDL, um nichts zu gaten. Dasselbe gilt
für @RTI_* und @RTPS_*. Wäre @Key als Key gelesen worden, stünde er
in der Tabelle oben.
Tests: tools/idlc/tests/strict.rs — die Matrix aus sechs
Unterbefehlen × vier Vendorerweiterungen × zwei Dialekten, plus die
Gegenprobe, dass dieselben Dateien ohne --strict durch jeden
Unterbefehl gehen; crates/idl-lower/tests/dialects.rs,
strict_rejects_every_vendor_extension_the_front_end_acts_on und
strict_does_not_refuse_an_unknown_annotation.
Proben: dialect-rti-keylist, dialect-cyclone-pragma-keylist,
dialect-opendds-pragma-datatype — strict jetzt err;
dialect-fastdds-key-capital bleibt ok.
Eine Probe war falsch gebaut. lex-7.3-pragma stand für §7.3, also
dafür, dass der Präprozessor #pragma überhaupt verarbeitet — und
benutzte dafür #pragma keylist Sa m, eine Cyclone-Direktive. Damit maß
sie zwei Achsen zugleich und wurde unter --strict aus einem Grund rot,
der mit §7.3 nichts zu tun hat. Sie schreibt jetzt #pragma vendorless Sa.
F — format nahm an, was check abwies (geschlossen)
Kein Befund über ein Konstrukt, sondern über einen Unterbefehl. Er fiel erst auf, als die Erhebung von zwei Spalten auf die Matrix über alle sechs erweitert wurde.
zerodds-idlc format lief nicht über dieselbe Pipeline wie check.
tools/idlc/src/format.rs, canonical(), ruft
zerodds_idl_parse::parse(source) — den nackten Parser. Kein
Präprozessor, keine Lowering-Stufe, keine Resolve-Stufe. Der Formatierer
wies eine Datei ab, die nicht parste; alles, was erst danach auffiel,
ging klaglos durch. Gemessen: 21 der 356 Proben hatten check err
und format ok, und über den committeten Baum 12 der 748
*.idl-Dateien. Der Nutzer bekam eine formatierte Datei zurück, die sein
Werkzeug im nächsten Schritt verwarf.
| Stufe, die den Fehler meldet | Proben |
|---|---|
| Präprozessor | lex-7.3-line-continuation, lex-7.3-nested-macro, lex-7.3-if-arithmetic, lex-7.3-predefined-line, lex-7.3-predefined-file |
| Lowering (Lexik/Scoping) | lex-7.2.3-case-collision, lex-7.2.3-keyword-collision, scope-7.5.2-enum-name-in-scope, scope-7.4-dup-case-label, scope-7.4-two-defaults, scope-7.4-enum-dup |
| Lowering (Semantik) | sem-7.4.13-bitmask-empty, sem-7.4.1-seq-bound-zero, sem-7.4.1-string-bound-zero, sem-7.4.1-array-size-zero, sem-7.4.1-array-size-negative, sem-8.3.1.1-id-collision, sem-8.3.1.2-autoid-bad-arg, sem-8.3.1.6-ext-bad-arg, sem-8.3.1.3-key-and-optional |
| Resolve | scope-7.4-unknown-type |
Der Fix. run_format in tools/idlc/src/main.rs läuft jetzt zuerst
durch analyse() — dieselbe Pipeline, die check fährt — und weist die
Datei ab, sobald ein Fehler gemeldet wird. Auf dem Erfolgspfad wird
nichts ausgegeben: der formatierte Text geht nach stdout, und eine
Warnung davor landete in der Datei, in die ein Aufrufer umlenkt.
Alle 21 Proben stehen jetzt auf format err. Die Bibliotheksfunktion
format::canonical_under behält ihren Vertrag auf Parse-Ebene; der
Corpus-Test in tools/idlc/src/format.rs misst weiter über sie.
Die andere Richtung gibt es nicht: keine Probe hat check ok und
format err. lint, dump-ast und print-deps laufen über analyse()
wie check und stimmen mit ihm in allen 357 Proben überein.
Round-Trip: über die 325 Proben, die check und format beide
annehmen, hält er vollständig — dasselbe Modell, dieselben
Abhängigkeiten, idempotentes Format. Kein stiller Verlust im
Formatierer. Das gilt ausdrücklich auch für die 26 Proben, die bis zu
dieser Fixture-Arbeit die Kategorie D ausmachten: der Formatierer
verliert keine davon.
Der Baum selbst
Die Erhebung misst Proben. Diese Seite hatte bis hierher keine Aussage
darüber, ob die 748 committeten *.idl-Dateien des Baums selbst gültiges
IDL sind — und es gab keinen Test, der check über sie alle fuhr. Ein
Konstrukt, das in einer eigenen Datei falsch wurde, fiel nur auf, wenn
zufällig ein anderer Test sie las.
Aufgefallen ist das an tools/wire-oracle/cases/bitmask_past_bound/type.idl:
absichtlich spezifikationswidrig, nur an fremde Generatoren gegeben, und
von einer Datei, die still falsch geworden ist, nicht zu unterscheiden.
Es brauchte eine neue Regel (B.4), damit sie überhaupt sichtbar wurde.
tools/idlc/tests/corpus.rs fährt jetzt check über jede committete
.idl. 13 Dateien werden abgewiesen, alle 13 mit einem
geschriebenen Grund in INTENDED, je Datei die erlaubten Codes und ein
Satz, der sagt, wofür die Datei da ist. Drei Gruppen:
| Gruppe | Dateien | Was sie sind |
|---|---|---|
| Probe-Eingaben | 6 | tools/hashid-gate (2), tools/inventory-probes (2), tools/try-construct-gate (2) — die Zurückweisung ist die Messung |
| Grammatik-Fixtures | 4 | ccm_components, corba_value_repository, fastdds_pragmas, zerodds_security — für Syntaxabdeckung kuratiert, nicht für ein Modell, das schließt |
| Wire-Messung | 1 | bitmask_past_bound — fragt vier Fremdvendoren, was sie mit verbotenem @position tun |
| ORB-Korpus | 1 | 05-typecode — CORBA::TypeCode, das jeder ORB vordeklariert |
| RTI-Fixture | — | war die 13., geht seit A.4 durch |
Ein zweiter Test prüft die Gegenrichtung: ein Eintrag, dessen Datei wieder durchgeht, ist veraltet und macht den Test rot. Eine Ausnahme ohne Grund lässt er gar nicht erst zu.
Ein echter Defekt kam dabei heraus.
examples/demos/perf-camera-dds/idl/camera.idl schrieb unsigned octet quality_hint; — unsigned octet steht in keiner Produktion von IDL 4.2,
octet ist bereits vorzeichenlos. Ein ausgeliefertes Beispiel, das kein
konformes Frontend übersetzt. Korrigiert zu octet quality_hint;.
Was diese Erhebung nicht abdeckt
- Emission nur in zwei Sprachen.
rustundc. Die übrigen Backends untercrates/lang-rules/src/default_*.rssind ungeprüft. - Kein Wire-Vergleich. Gemessen wurde, ob Quelltext entsteht, nicht ob die erzeugten Bytes stimmen. Das ist die Frage der Conformance-Fixtures und beantwortet diese Seite nicht.
fixturesist Textsuche. Ein Treffer belegt nicht, dass die Datei in einem Test benutzt wird. Eine Null belegt, dass das Konstrukt im Baum nicht vorkommt.- Kein Vergleich mit anderen Frontends. Ob
fastddsgen,idlcvon Cyclone odertao_idldieselbe IDL nehmen, ist nicht gemessen. - Klausel 9 (Profile). Die vier CORBA/CCM- und drei DDS-Profile sind
Zusammenstellungen der Building Blocks; ob
zerodds-idlcein Profil-Argument braucht, ist eine Produktfrage und keine Lücke gegen die Grammatik. Ungeprüft. - Tabelle 7-6 als Ganzes. Die Spezifikation listet 84 Keywords.
Geprüft ist ein Vertreter (
typedef boolean BOOLEAN;wird mit[IDL0017]abgewiesen) und die Escape-Form (long _long;wird akzeptiert, 150 Fixtures im Baum benutzen sie). Die übrigen 83 Keywords sind nicht einzeln als Bezeichner probiert. Auch §7.2.1 Tokens und §7.2.5 Punctuation sind nicht Zeichen für Zeichen belegt.
Das Werkzeug
tools/idl-4.2-gap-scan/scan.sh messen und vergleichen
tools/idl-4.2-gap-scan/scan.sh --record expected.tsv neu schreiben
tools/idl-4.2-gap-scan/scan.sh --report die volle Messung ausgeben
IDLC=/pfad/zu/zerodds-idlc tools/idl-4.2-gap-scan/scan.sh
Exit-Codes:
| 0 | gemessen; die Lücke ist genau die aus expected.tsv |
| 1 | eine Probe, die durchkam, kommt nicht mehr durch — die Lücke ist gewachsen |
| 2 | nichts gemessen: kein zerodds-idlc. Nie ein Pass. |
| 3 | eine Probe kommt jetzt durch, die expected.tsv als scheiternd führt — die Lücke ist geschrumpft und diese Seite ist veraltet |
| 4 | cases.tsv und expected.tsv nennen verschiedene Proben |
Laufzeit auf einem M1: 36 Sekunden für 356 Proben über 748
.idl-Dateien. Vor der Matrix waren es 16 Sekunden bei zwei gemessenen
Unterbefehlen; die elf Aufrufe je Probe kosten den Rest.
Exit 3 ist nicht bloß Kosmetik. Eine Negativliste, die eine geschlossene Lücke weiter als offen führt, ist schlechter als keine: sie kostet Vertrauen in die übrigen Zeilen.
Methodennotiz
Übertragbar auf CORBA 3.3, die DDS-Specs und die 47 eigenen Specs unter
docs/specs/ — mit sehr verschiedenen Kosten.
Was billig war. Der Nenner. IDL 4.2 hat Annex A: eine konsolidierte,
durchnummerierte Grammatik, aus der sich mit pdftotext -layout und
zwanzig Zeilen Python alle 227 Produktionen lückenlos ziehen lassen
(Lückenprobe: range(1, 228) gegen die geparsten Nummern). Ohne diesen
Annex hätte das Aufsammeln der Produktionen aus fünfzehn Building-Block-
Klauseln ein Vielfaches gekostet, und die Vollständigkeit wäre nicht
prüfbar gewesen.
Was teuer war. Die Proben. 356 minimale .idl-Dateien von Hand, und
zweimal eine Runde Nacharbeit, weil die ersten Fassungen aus dem
falschen Grund scheiterten: ein Bezeichner Alias kollidiert mit dem
Keyword alias, und eine Union ohne default: wird vom Backend aus
einem Grund abgelehnt, der mit der geprüften Produktion nichts zu tun
hat. Jede Fehlmessung sah aus wie ein Befund. Das ist die eigentliche
Arbeit an so einer Erhebung: unterscheiden, ob die Probe oder der Baum
falsch ist.
Wo der Volltext gebraucht wird. Für die Kategorien A, C, D reicht die
Grammatik. Für Kategorie B — akzeptiert, obwohl verboten — braucht es den
Fließtext, und zwar wörtlich. Alle 19 B-Zeilen stammen aus Sätzen, die in
keiner Produktion stehen: „Adjacent string literals are concatenated”,
„The maximum value is 64”, „Members with no default value must be given a
value”. Ohne PDF im Volltext wären sie unauffindbar gewesen. Und dieselbe
Klausel hat zwei Behauptungen widerlegt, die ich sonst als Lücke
aufgeschrieben hätte: unbekannte Annotationen sollen ignoriert werden,
und struct S { long s; } ist ein Fehler. Ohne den Text wären das zwei
falsche Zeilen gewesen.
Was das für die anderen Specs heißt.
| Korpus | Nenner vorhanden? | geschätzter Aufwand |
|---|---|---|
| CORBA 3.3 | Ja — Part 1 Annex A trägt dieselbe konsolidierte Grammatik, plus die GIOP/IIOP-Nachrichtentypen als Tabellen. Der Fließtext ist mit ~1.150 Seiten über drei Teile aber achtmal so lang wie IDL 4.2. | Grammatikachse etwa wie hier, ein bis zwei Tage. Die B-Achse (verbotene Formen) ist der Kostentreiber und pro Teil separat zu machen. Die Proben brauchen einen anderen Treiber, weil zerodds-idlc nicht die ganze Fläche abdeckt: für GIOP/POA/IR muss die Probe ein Rust-Test sein, keine .idl-Datei. |
| DDS-Specs (DCPS 1.4, XTypes 1.3, DDSI-RTPS 2.5, Security 1.2) | Nein, nicht in dieser Form. Der Nenner ist dort keine Grammatik, sondern eine Menge von Tabellen: QoS-Policies, Submessage-Kinds, TypeKinds, Plugin-Operationen. Die sind maschinenlesbar aus dem PDF zu holen, aber jede Spec braucht ihren eigenen Extraktor. | Pro Spec ein Tag für den Extraktor plus die Proben. Der Vorteil: die Proben sind hier keine Textdateien, sondern Bytes, und tools/byte-gate und tools/unspecified-reach sind die vorhandenen Treiber dafür. Die vier Kategorien werden zu: kennt der Decoder das Feld / schreibt der Encoder es / gibt es einen Testvektor / ist es bewusst nicht implementiert. |
Eigene Specs unter docs/specs/ |
Ja, und billiger als bei allen anderen — die 47 Dokumente sind Markdown mit nummerierten Klauseln im Baum. Ein Extraktor für alle 47. | Der Nenner ist in Stunden zu haben. Der Haken ist ein anderer: eine selbst geschriebene Spec kann nicht gegen sich selbst falsch sein. Was eine Negativliste dort findet, ist eine Klausel ohne Implementierung — nützlich, aber nicht dasselbe wie eine Lücke gegen einen fremden Nenner. Der Erkenntniswert steht hinter CORBA und den DDS-Specs. |
Die Reihenfolge, die ich empfehle. DDS-XTypes 1.3 vor CORBA 3.3: der Nenner ist kleiner, die Proben sind Bytes statt Text, und der Wire-Bereich ist der, in dem ein übersehenes Konstrukt still falsche Daten liefert statt eine Fehlermeldung. Die eigenen Specs zuletzt.
Eine Regel, die die Erhebung selbst gelernt hat. „Kommt im Baum nicht vor” ist kein Beleg für „ist nicht erlaubt” — das war der Anlass. Die Umkehrung gilt aber auch: „das Frontend weist es ab” ist kein Beleg für „die Spezifikation verbietet es”. Beide Richtungen brauchen den Spec-Text, und beide sind in dieser Erhebung vorgekommen.
Decision-Records
Keine. Diese Seite erhebt und schließt nichts.