Skip to content

Instantly share code, notes, and snippets.

@MolecularMatters
Last active January 20, 2022 13:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MolecularMatters/7300b347d8294aaf49b7eebe8c2ab80f to your computer and use it in GitHub Desktop.
Save MolecularMatters/7300b347d8294aaf49b7eebe8c2ab80f to your computer and use it in GitHub Desktop.
Safely casting records of different types to correct class types
#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