Skip to content

Commit b32d7cc

Browse files
committed
libdxfrw: check the typed class table's invariants at compile time
With the registrars driven from one table, the properties they used to hold simply by being written out one at a time are no longer guaranteed by construction. Assert them instead: every row needs a non-empty key, application name, class name and record name, and the key, record name and class name have to be unique. A copy-paste slip in a new row now fails the build rather than silently registering the wrong metadata. Class numbers are deliberately excluded. The check was written to require them to be unique too, and that immediately failed the build: MLEADERSTYLE shares 504 with MATERIAL, TABLESTYLE shares 509 with IDBUFFER, and EVALUATION_GRAPH shares 516 with FIELD. The custom-class range is assigned per file and registerTypedObjectClass() hands a colliding type the next free number, so those defaults are a starting point rather than an identity. That is now recorded at the table.
1 parent 1fcc8a6 commit b32d7cc

1 file changed

Lines changed: 54 additions & 0 deletions

File tree

libraries/libdxfrw/src/intern/dwgwriter.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,60 @@ inline constexpr DwgTypedClassRow kDwgTypedClassRows[] = {
135135
{"Field", DRW_Field::kDwgClassNum, 0x401, "ACAD", "AcDbField", "FIELD", 0x1F3},
136136
};
137137

138+
// The table above is now the single source of truth for those registrars, so
139+
// the properties they used to hold simply by being written out one at a time
140+
// are checked here at compile time instead: every row needs a non-empty key,
141+
// application name, class name and record name, and the key, record name and
142+
// class name have to be unique. A copy-paste slip in a new row then fails the
143+
// build instead of silently registering the wrong metadata.
144+
//
145+
// Class numbers are deliberately NOT required to be unique. The custom-class
146+
// range (>= 500) is assigned per file, and several types share a default here
147+
// - MLEADERSTYLE with MATERIAL at 504, TABLESTYLE with IDBUFFER at 509, and
148+
// EVALUATION_GRAPH with FIELD at 516. registerTypedObjectClass() resolves a
149+
// collision by handing the later type the next free number, so the defaults
150+
// are a starting point rather than an identity.
151+
constexpr bool dwgClassTextEqual(const char* a, const char* b) {
152+
if (a == nullptr || b == nullptr)
153+
return a == b;
154+
while (*a != '\0' && *a == *b) {
155+
++a;
156+
++b;
157+
}
158+
return *a == *b;
159+
}
160+
161+
constexpr bool dwgClassTextNonEmpty(const char* text) {
162+
return text != nullptr && *text != '\0';
163+
}
164+
165+
constexpr bool dwgTypedClassRowsAreWellFormed() {
166+
constexpr std::size_t count =
167+
sizeof(kDwgTypedClassRows) / sizeof(kDwgTypedClassRows[0]);
168+
for (std::size_t i = 0; i < count; ++i) {
169+
const DwgTypedClassRow& a = kDwgTypedClassRows[i];
170+
if (!dwgClassTextNonEmpty(a.key) || !dwgClassTextNonEmpty(a.appName)
171+
|| !dwgClassTextNonEmpty(a.className)
172+
|| !dwgClassTextNonEmpty(a.recordName))
173+
return false;
174+
for (std::size_t j = i + 1; j < count; ++j) {
175+
const DwgTypedClassRow& b = kDwgTypedClassRows[j];
176+
if (dwgClassTextEqual(a.key, b.key)
177+
|| dwgClassTextEqual(a.recordName, b.recordName)
178+
|| dwgClassTextEqual(a.className, b.className))
179+
return false;
180+
}
181+
}
182+
return true;
183+
}
184+
185+
static_assert(dwgTypedClassRowsAreWellFormed(),
186+
"kDwgTypedClassRows: every row needs a non-empty key, app name, "
187+
"class name and record name, and the key, record name and class "
188+
"name must each be unique across the table (class numbers may "
189+
"collide and are remapped at registration)");
190+
191+
138192
class dwgWriter {
139193
public:
140194
/// Construct around an output stream and a populated header.

0 commit comments

Comments
 (0)