Last active
January 20, 2022 13:21
-
-
Save MolecularMatters/7300b347d8294aaf49b7eebe8c2ab80f to your computer and use it in GitHub Desktop.
Safely casting records of different types to correct class types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstdint> | |
#include <assert.h> | |
/* | |
base approach: each class corresponds to the layout of a symbol of a certain kind. | |
however, there is no relationship between any of the SymbolRecordKind values and the corresponding class layout. | |
this leads to more copy-and-paste errors and makes it harder to enforce that the underlying data is cast correctly. | |
*/ | |
enum class SymbolRecordKind : uint16_t | |
{ | |
S_PUB32 = 0x110Eu, | |
S_GDATA32 = 0x110Du, | |
S_OBJNAME = 0x1101u | |
// ... | |
}; | |
struct PublicSymbol | |
{ | |
uint32_t flags; | |
uint32_t offset; | |
uint16_t section; | |
char name[1]; | |
}; | |
struct GlobalSymbol | |
{ | |
uint32_t typeIndex; | |
uint32_t offset; | |
uint16_t section; | |
char name[1]; | |
}; | |
struct ObjNameSymbol | |
{ | |
uint32_t signature; | |
char name[1]; | |
}; | |
void TransformData(const void* data, SymbolRecordKind kind) | |
{ | |
switch (kind) | |
{ | |
case SymbolRecordKind::S_PUB32: | |
{ | |
const PublicSymbol* symbol = static_cast<const PublicSymbol*>(data); | |
// ... | |
} | |
break; | |
case SymbolRecordKind::S_GDATA32: | |
{ | |
const GlobalSymbol* symbol = static_cast<const GlobalSymbol*>(data); | |
// ... | |
} | |
break; | |
case SymbolRecordKind::S_OBJNAME: | |
{ | |
const ObjNameSymbol* symbol = static_cast<const ObjNameSymbol*>(data); | |
// ... | |
} | |
break; | |
// ... | |
} | |
} | |
/* | |
different approach that builds a relationship between values and corresponding types through the type system: | |
each symbol record corresponds to exactly *one* layout that is dictated by the template specialization for that value. | |
fewer copy-and-paste errors, can be macro-ified easier because the actual value becomes part of the type, easier to assert correct casts. | |
*/ | |
template <SymbolRecordKind Kind> | |
struct SymbolRecord {}; | |
template <> | |
struct SymbolRecord<SymbolRecordKind::S_PUB32> | |
{ | |
uint32_t flags; | |
uint32_t offset; | |
uint16_t section; | |
char name[1]; | |
}; | |
template <> | |
struct SymbolRecord<SymbolRecordKind::S_GDATA32> | |
{ | |
uint32_t typeIndex; | |
uint32_t offset; | |
uint16_t section; | |
char name[1]; | |
}; | |
template <> | |
struct SymbolRecord<SymbolRecordKind::S_OBJNAME> | |
{ | |
uint32_t signature; | |
char name[1]; | |
}; | |
// cast that enforces that the runtime kind matches the compile-time kind of record | |
template <SymbolRecordKind Kind> | |
const SymbolRecord<Kind>* CastData(const void* data, SymbolRecordKind kind) | |
{ | |
assert(Kind == kind); | |
return static_cast<const SymbolRecord<Kind>*>(data); | |
} | |
void TransformData2(const void* data, SymbolRecordKind kind) | |
{ | |
switch (kind) | |
{ | |
// note that SymbolRecordKind::S_PUB32 comes up in the case statement as well as the CastData<> type, | |
// so this is trivial to put in a macro, if you want. | |
case SymbolRecordKind::S_PUB32: | |
{ | |
const auto* symbol = CastData<SymbolRecordKind::S_PUB32>(data, kind); | |
// ... | |
} | |
break; | |
case SymbolRecordKind::S_GDATA32: | |
{ | |
const auto* symbol = CastData<SymbolRecordKind::S_GDATA32>(data, kind); | |
// ... | |
} | |
break; | |
case SymbolRecordKind::S_OBJNAME: | |
{ | |
const auto* symbol = CastData<SymbolRecordKind::S_OBJNAME>(data, kind); | |
// ... | |
} | |
break; | |
// ... | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment