mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-22 21:22:41 -04:00
Mod API: Aggregate-initialize import/export records
Starting with VS 18 / cl 14.51, MSVC's constant evaluator rejects a pointer-to-member in a constant initializer that also contains any computed data, a consteval call or even a copy of a constexpr object (C2127). Hook records are now aggregate-initialized from literals only: the computed name bytes (vtable symbol + display name) are baked into char... template arguments and sizes/kinds are spelled inline.
This commit is contained in:
+6
-2
@@ -156,6 +156,10 @@ typedef struct ModMetaRecord {
|
||||
uint8_t flags; /* ServiceImportFlags / ServiceExportFlags for imports/exports */
|
||||
} ModMetaRecord;
|
||||
|
||||
typedef struct ModMetaServiceId {
|
||||
char chars[MOD_META_SERVICE_ID_SIZE]; /* NUL-terminated */
|
||||
} ModMetaServiceId;
|
||||
|
||||
typedef struct MOD_META_ALIGN ModMetaHeader {
|
||||
ModMetaRecord rec;
|
||||
uint32_t abi_version;
|
||||
@@ -168,7 +172,7 @@ typedef struct MOD_META_ALIGN ModMetaImport {
|
||||
uint16_t major_version;
|
||||
uint16_t min_minor_version;
|
||||
void* slot; /* runtime only */
|
||||
char service_id[MOD_META_SERVICE_ID_SIZE];
|
||||
ModMetaServiceId service_id;
|
||||
} ModMetaImport;
|
||||
|
||||
static_assert(sizeof(ModMetaImport) == 16 + MOD_META_SERVICE_ID_SIZE);
|
||||
@@ -178,7 +182,7 @@ typedef struct MOD_META_ALIGN ModMetaExport {
|
||||
uint16_t major_version;
|
||||
uint16_t minor_version;
|
||||
const void* service; /* runtime only */
|
||||
char service_id[MOD_META_SERVICE_ID_SIZE];
|
||||
ModMetaServiceId service_id;
|
||||
} ModMetaExport;
|
||||
|
||||
static_assert(sizeof(ModMetaExport) == 16 + MOD_META_SERVICE_ID_SIZE);
|
||||
|
||||
@@ -136,10 +136,14 @@ struct NamedHook<Name, R(A...)> : HookImpl<detail::NameTag<Name>, R, A...> {};
|
||||
* ambiguous and need the mangled form.
|
||||
*/
|
||||
#define DEFINE_HOOK(target, alias) \
|
||||
MOD_META_RECORD static constinit auto mod_meta_hook_##alias = \
|
||||
::dusk::mods::detail::make_hook_record<(target), ::dusk::mods::FixedString{#target}>(); \
|
||||
[[maybe_unused]] static const void* const mod_meta_hook_##alias = \
|
||||
&::dusk::mods::detail::HookRecordFor<(target), \
|
||||
::dusk::mods::FixedString{#target}>::Holder::record; \
|
||||
struct alias : ::dusk::mods::Hook<(target)> { \
|
||||
static void* resolved_target() { return mod_meta_hook_##alias.resolved; } \
|
||||
static void* resolved_target() { \
|
||||
return ::dusk::mods::detail::HookRecordFor<(target), \
|
||||
::dusk::mods::FixedString{#target}>::Holder::record.resolved; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DEFINE_HOOK_SYMBOL(name, sig, alias) \
|
||||
|
||||
+66
-59
@@ -6,6 +6,7 @@
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
/*
|
||||
* modmeta records. Each IMPORT_SERVICE/EXPORT_SERVICE/DEFINE_HOOK use places one
|
||||
@@ -141,44 +142,16 @@ struct member_traits<R (C::*)(A...) const> {
|
||||
using Class = C;
|
||||
};
|
||||
|
||||
consteval size_t cstr_len(const char* s) {
|
||||
size_t n = 0;
|
||||
while (s[n] != '\0') {
|
||||
++n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
consteval void copy_service_id(char (&dst)[MOD_META_SERVICE_ID_SIZE], const char* id) {
|
||||
consteval ModMetaServiceId make_service_id(const char* id) {
|
||||
ModMetaServiceId out{};
|
||||
size_t n = 0;
|
||||
for (; id[n] != '\0'; ++n) {
|
||||
if (n + 1 >= MOD_META_SERVICE_ID_SIZE) {
|
||||
throw "service id exceeds MOD_META_SERVICE_ID_SIZE";
|
||||
}
|
||||
dst[n] = id[n];
|
||||
out.chars[n] = id[n];
|
||||
}
|
||||
}
|
||||
|
||||
consteval ModMetaImport make_import(
|
||||
const char* serviceId, uint16_t major, uint16_t minMinor, uint8_t flags, void* slot) {
|
||||
ModMetaImport r{};
|
||||
r.rec = {sizeof(ModMetaImport), MOD_META_IMPORT, flags};
|
||||
r.major_version = major;
|
||||
r.min_minor_version = minMinor;
|
||||
r.slot = slot;
|
||||
copy_service_id(r.service_id, serviceId);
|
||||
return r;
|
||||
}
|
||||
|
||||
consteval ModMetaExport make_export(
|
||||
const char* serviceId, uint16_t major, uint16_t minor, uint8_t flags, const void* service) {
|
||||
ModMetaExport r{};
|
||||
r.rec = {sizeof(ModMetaExport), MOD_META_EXPORT, flags};
|
||||
r.major_version = major;
|
||||
r.minor_version = minor;
|
||||
r.service = service;
|
||||
copy_service_id(r.service_id, serviceId);
|
||||
return r;
|
||||
return out;
|
||||
}
|
||||
|
||||
consteval ModMetaHeader make_header() {
|
||||
@@ -227,40 +200,74 @@ constexpr size_t align_up(size_t n) {
|
||||
return (n + (N - 1)) & ~(N - 1);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
struct HookMemNames {
|
||||
char chars[N]{};
|
||||
size_t len{};
|
||||
};
|
||||
|
||||
template <auto Target, FixedString Disp>
|
||||
consteval auto make_hook_record() {
|
||||
using F = decltype(Target);
|
||||
consteval auto make_hook_mem_names() {
|
||||
using C = member_traits<decltype(Target)>::Class;
|
||||
// Strip the leading '&' of the stringified target expression for display.
|
||||
constexpr size_t dispFrom = Disp.chars[0] == '&' ? 1 : 0;
|
||||
constexpr size_t dispLen = sizeof(Disp.chars) - 1 - dispFrom;
|
||||
if constexpr (std::is_member_function_pointer_v<F>) {
|
||||
using C = member_traits<F>::Class;
|
||||
static_assert(sizeof(F) <= 16, "unsupported pointer-to-member representation");
|
||||
constexpr auto vtbl = vtable_symbol<C>();
|
||||
constexpr size_t vtblLen = std::string_view{vtbl.data()}.size();
|
||||
constexpr size_t n = align_up<8>(vtblLen + 1 + dispLen + 1);
|
||||
HookMemRecord<F, n> r{};
|
||||
r.rec = {sizeof(r), MOD_META_HOOK_MEM, 0};
|
||||
r.pmf.fn = Target;
|
||||
size_t at = 0;
|
||||
for (size_t i = 0; i < vtblLen; ++i) {
|
||||
r.names[at++] = vtbl[i];
|
||||
}
|
||||
r.names[at++] = '\0';
|
||||
for (size_t i = 0; i < dispLen; ++i) {
|
||||
r.names[at++] = Disp.chars[dispFrom + i];
|
||||
}
|
||||
return r;
|
||||
} else {
|
||||
static_assert(std::is_pointer_v<F> && std::is_function_v<std::remove_pointer_t<F>>,
|
||||
"hook target must be a function or member function");
|
||||
HookFnRecord<F> r{};
|
||||
r.rec = {sizeof(r), MOD_META_HOOK_FN, 0};
|
||||
r.target = Target;
|
||||
return r;
|
||||
constexpr auto vtbl = vtable_symbol<C>();
|
||||
constexpr size_t vtblLen = std::string_view{vtbl.data()}.size();
|
||||
HookMemNames<align_up<8>(vtblLen + 1 + dispLen + 1)> r{};
|
||||
size_t at = 0;
|
||||
for (size_t i = 0; i < vtblLen; ++i) {
|
||||
r.chars[at++] = vtbl[i];
|
||||
}
|
||||
r.chars[at++] = '\0';
|
||||
for (size_t i = 0; i < dispLen; ++i) {
|
||||
r.chars[at++] = Disp.chars[dispFrom + i];
|
||||
}
|
||||
r.len = sizeof(r.chars);
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* MSVC constant-evaluates a pointer-to-member only when every other operand in the
|
||||
* initializer is a literal: no consteval calls, constexpr-object copies, or default
|
||||
* member initializers.
|
||||
*/
|
||||
template <auto Target, char... Cs>
|
||||
struct HookMemHolder {
|
||||
using F = decltype(Target);
|
||||
static_assert(sizeof(F) <= 16, "unsupported pointer-to-member representation");
|
||||
MOD_META_RECORD static constinit inline HookMemRecord<F, sizeof...(Cs)> record = {
|
||||
{sizeof(HookMemRecord<F, sizeof...(Cs)>), MOD_META_HOOK_MEM, 0}, 0, {Target}, nullptr,
|
||||
{Cs...}};
|
||||
};
|
||||
|
||||
template <auto Target>
|
||||
struct HookFnHolder {
|
||||
using F = decltype(Target);
|
||||
static_assert(std::is_pointer_v<F> && std::is_function_v<std::remove_pointer_t<F>>,
|
||||
"hook target must be a function or member function");
|
||||
MOD_META_RECORD static constinit inline HookFnRecord<F> record = {
|
||||
{sizeof(HookFnRecord<F>), MOD_META_HOOK_FN, 0}, 0, Target, nullptr};
|
||||
};
|
||||
|
||||
template <auto Target, FixedString Disp,
|
||||
bool = std::is_member_function_pointer_v<decltype(Target)>>
|
||||
struct HookRecordFor {
|
||||
using Holder = HookFnHolder<Target>;
|
||||
};
|
||||
|
||||
template <auto Target, FixedString Disp>
|
||||
struct HookRecordFor<Target, Disp, true> {
|
||||
template <class Seq>
|
||||
struct Bind;
|
||||
template <size_t... Is>
|
||||
struct Bind<std::index_sequence<Is...>> {
|
||||
using Type = HookMemHolder<Target, make_hook_mem_names<Target, Disp>().chars[Is]...>;
|
||||
};
|
||||
using Holder =
|
||||
Bind<std::make_index_sequence<make_hook_mem_names<Target, Disp>().len>>::Type;
|
||||
};
|
||||
|
||||
template <FixedString Name>
|
||||
consteval auto make_hook_name_record() {
|
||||
constexpr size_t len = sizeof(Name.chars) - 1;
|
||||
|
||||
+21
-10
@@ -46,10 +46,13 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
|
||||
#define IMPORT_SERVICE_EX( \
|
||||
service_type, variable, service_id_value, major_value, min_minor_value, flags_value) \
|
||||
static const service_type* variable = nullptr; \
|
||||
MOD_META_RECORD static constinit ModMetaImport mod_meta_import_##variable = \
|
||||
::dusk::mods::detail::make_import((service_id_value), static_cast<uint16_t>(major_value), \
|
||||
static_cast<uint16_t>(min_minor_value), static_cast<uint8_t>(flags_value), \
|
||||
&(variable))
|
||||
MOD_META_RECORD static constinit ModMetaImport mod_meta_import_##variable = { \
|
||||
{sizeof(ModMetaImport), MOD_META_IMPORT, static_cast<uint8_t>(flags_value)}, \
|
||||
static_cast<uint16_t>(major_value), \
|
||||
static_cast<uint16_t>(min_minor_value), \
|
||||
&(variable), \
|
||||
::dusk::mods::detail::make_service_id(service_id_value), \
|
||||
}
|
||||
|
||||
#define IMPORT_SERVICE_VERSION(service_type, variable, min_minor_value) \
|
||||
IMPORT_SERVICE_EX(service_type, variable, ::dusk::mods::ServiceTraits<service_type>::id, \
|
||||
@@ -67,15 +70,23 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
|
||||
IMPORT_OPTIONAL_SERVICE_VERSION(service_type, variable, 0)
|
||||
|
||||
#define EXPORT_SERVICE_AS(instance, service_id_value) \
|
||||
MOD_META_RECORD static constinit ModMetaExport mod_meta_export_##instance = \
|
||||
::dusk::mods::detail::make_export((service_id_value), (instance).header.major_version, \
|
||||
(instance).header.minor_version, SERVICE_EXPORT_STATIC, &(instance))
|
||||
MOD_META_RECORD static constinit ModMetaExport mod_meta_export_##instance = { \
|
||||
{sizeof(ModMetaExport), MOD_META_EXPORT, SERVICE_EXPORT_STATIC}, \
|
||||
(instance).header.major_version, \
|
||||
(instance).header.minor_version, \
|
||||
&(instance), \
|
||||
::dusk::mods::detail::make_service_id(service_id_value), \
|
||||
}
|
||||
|
||||
#define EXPORT_SERVICE(instance) \
|
||||
EXPORT_SERVICE_AS( \
|
||||
instance, ::dusk::mods::ServiceTraits<std::remove_cv_t<decltype(instance)>>::id)
|
||||
|
||||
#define EXPORT_DEFERRED_SERVICE(token, service_id_value, major_value, minor_value) \
|
||||
MOD_META_RECORD static constinit ModMetaExport mod_meta_export_##token = \
|
||||
::dusk::mods::detail::make_export((service_id_value), static_cast<uint16_t>(major_value), \
|
||||
static_cast<uint16_t>(minor_value), SERVICE_EXPORT_DEFERRED, nullptr)
|
||||
MOD_META_RECORD static constinit ModMetaExport mod_meta_export_##token = { \
|
||||
{sizeof(ModMetaExport), MOD_META_EXPORT, SERVICE_EXPORT_DEFERRED}, \
|
||||
static_cast<uint16_t>(major_value), \
|
||||
static_cast<uint16_t>(minor_value), \
|
||||
nullptr, \
|
||||
::dusk::mods::detail::make_service_id(service_id_value), \
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ static bool parse_meta(NativeMod& native, LoadedMod& mod) {
|
||||
return invalid("truncated import record");
|
||||
}
|
||||
auto* record = reinterpret_cast<ModMetaImport*>(const_cast<uint8_t*>(cursor));
|
||||
if (!terminated_within(record->service_id, sizeof(record->service_id))) {
|
||||
if (!terminated_within(record->service_id.chars, sizeof(record->service_id.chars))) {
|
||||
return invalid("unterminated import service id");
|
||||
}
|
||||
parsed.imports.push_back(record);
|
||||
@@ -299,7 +299,7 @@ static bool parse_meta(NativeMod& native, LoadedMod& mod) {
|
||||
return invalid("truncated export record");
|
||||
}
|
||||
auto* record = reinterpret_cast<ModMetaExport*>(const_cast<uint8_t*>(cursor));
|
||||
if (!terminated_within(record->service_id, sizeof(record->service_id))) {
|
||||
if (!terminated_within(record->service_id.chars, sizeof(record->service_id.chars))) {
|
||||
return invalid("unterminated export service id");
|
||||
}
|
||||
parsed.exports.push_back(record);
|
||||
@@ -552,18 +552,18 @@ static ModManifestInfo build_manifest_info(const ModMetaParsed& parsed) {
|
||||
ModManifestInfo info;
|
||||
info.imports.reserve(parsed.imports.size());
|
||||
for (const auto* record : parsed.imports) {
|
||||
if (!svc::valid_service_id(record->service_id)) {
|
||||
if (!svc::valid_service_id(record->service_id.chars)) {
|
||||
continue;
|
||||
}
|
||||
info.imports.push_back({record->service_id, record->major_version,
|
||||
info.imports.push_back({record->service_id.chars, record->major_version,
|
||||
(record->rec.flags & SERVICE_IMPORT_OPTIONAL) == 0});
|
||||
}
|
||||
info.exports.reserve(parsed.exports.size());
|
||||
for (const auto* record : parsed.exports) {
|
||||
if (!svc::valid_service_id(record->service_id)) {
|
||||
if (!svc::valid_service_id(record->service_id.chars)) {
|
||||
continue;
|
||||
}
|
||||
info.exports.push_back({record->service_id, record->major_version});
|
||||
info.exports.push_back({record->service_id.chars, record->major_version});
|
||||
}
|
||||
return info;
|
||||
}
|
||||
@@ -608,11 +608,11 @@ static void warn_unpublished_deferred_exports(const LoadedMod& mod) {
|
||||
continue;
|
||||
}
|
||||
const auto* record =
|
||||
svc::find_service_record(serviceExport->service_id, serviceExport->major_version);
|
||||
svc::find_service_record(serviceExport->service_id.chars, serviceExport->major_version);
|
||||
if (record != nullptr && record->service == nullptr) {
|
||||
log::write(mod.metadata.id, LOG_LEVEL_WARN,
|
||||
"declared deferred service '{}@{}' but never published it during initialization",
|
||||
serviceExport->service_id, serviceExport->major_version);
|
||||
serviceExport->service_id.chars, serviceExport->major_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ bool ModLoader::register_static_service_exports(LoadedMod& mod) {
|
||||
}
|
||||
|
||||
for (const auto* serviceExport : mod.native->parsed.exports) {
|
||||
if (!svc::valid_service_id(serviceExport->service_id)) {
|
||||
if (!svc::valid_service_id(serviceExport->service_id.chars)) {
|
||||
fail_mod(mod, MOD_INVALID_ARGUMENT, "Invalid service export descriptor");
|
||||
return false;
|
||||
}
|
||||
@@ -234,7 +234,7 @@ bool ModLoader::register_static_service_exports(LoadedMod& mod) {
|
||||
}
|
||||
|
||||
const auto result =
|
||||
svc::register_service(serviceExport->service_id, serviceExport->major_version,
|
||||
svc::register_service(serviceExport->service_id.chars, serviceExport->major_version,
|
||||
serviceExport->minor_version, serviceExport->service, &mod, deferred);
|
||||
if (result != MOD_OK) {
|
||||
fail_mod(mod, result, "Service export registration failed");
|
||||
@@ -262,8 +262,8 @@ std::string ModLoader::describe_missing_import(
|
||||
continue;
|
||||
}
|
||||
for (const auto* serviceExport : other.native->parsed.exports) {
|
||||
if (svc::valid_service_id(serviceExport->service_id) &&
|
||||
std::string_view{serviceExport->service_id} == serviceId &&
|
||||
if (svc::valid_service_id(serviceExport->service_id.chars) &&
|
||||
std::string_view{serviceExport->service_id.chars} == serviceId &&
|
||||
serviceExport->major_version == majorVersion)
|
||||
{
|
||||
return fmt::format("Required service {}@{} unavailable: provider '{}' {}",
|
||||
@@ -282,12 +282,14 @@ bool ModLoader::resolve_service_imports(LoadedMod& mod) {
|
||||
}
|
||||
|
||||
for (const auto* serviceImport : mod.native->parsed.imports) {
|
||||
if (!svc::valid_service_id(serviceImport->service_id) || serviceImport->slot == nullptr) {
|
||||
if (!svc::valid_service_id(serviceImport->service_id.chars) ||
|
||||
serviceImport->slot == nullptr)
|
||||
{
|
||||
fail_mod(mod, MOD_INVALID_ARGUMENT, "Invalid service import descriptor");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* service = svc::find_service(serviceImport->service_id,
|
||||
const auto* service = svc::find_service(serviceImport->service_id.chars,
|
||||
serviceImport->major_version, serviceImport->min_minor_version);
|
||||
if (service == nullptr) {
|
||||
*static_cast<const void**>(serviceImport->slot) = nullptr;
|
||||
@@ -296,8 +298,8 @@ bool ModLoader::resolve_service_imports(LoadedMod& mod) {
|
||||
}
|
||||
|
||||
fail_mod(mod, MOD_UNAVAILABLE,
|
||||
describe_missing_import(serviceImport->service_id, serviceImport->major_version,
|
||||
serviceImport->min_minor_version));
|
||||
describe_missing_import(serviceImport->service_id.chars,
|
||||
serviceImport->major_version, serviceImport->min_minor_version));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user