fix nlohmann namespace injection of optional adapters is ODR-fragile (cpp.nim:66–78). Every header that does this races every other header that does the same. In a small

project it's invisible; in something the size of nwaku's consumer surface i2t will eventually collide. Either gate behind a feature macro, or use nlohmann's adl_serializer
  specialization which is the documented extension point.
This commit is contained in:
Ivan FB 2026-05-11 21:52:00 +02:00
parent 7764b2f43b
commit 6920f22ab8
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
2 changed files with 43 additions and 20 deletions

View File

@ -13,19 +13,26 @@
#include <optional>
#include <nlohmann/json.hpp>
#if !defined(NIM_FFI_NO_OPTIONAL_SERIALIZER) \
&& !defined(NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_) \
&& (!defined(NLOHMANN_JSON_VERSION_MAJOR) \
|| (NLOHMANN_JSON_VERSION_MAJOR < 3) \
|| (NLOHMANN_JSON_VERSION_MAJOR == 3 && NLOHMANN_JSON_VERSION_MINOR < 12))
#define NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_
namespace nlohmann {
template<typename T>
void to_json(json& j, const std::optional<T>& opt) {
if (opt) j = *opt;
else j = nullptr;
}
template<typename T>
void from_json(const json& j, std::optional<T>& opt) {
if (j.is_null()) opt = std::nullopt;
else opt = j.get<T>();
}
struct adl_serializer<std::optional<T>> {
static void to_json(json& j, const std::optional<T>& opt) {
if (opt) j = *opt;
else j = nullptr;
}
static void from_json(const json& j, std::optional<T>& opt) {
if (j.is_null()) opt = std::nullopt;
else opt = j.get<T>();
}
};
}
#endif
// ============================================================
// Types

View File

@ -64,19 +64,35 @@ proc generateCppHeader*(
lines.add("")
# ── nlohmann optional<T> support ──────────────────────────────────────────
# Use nlohmann's documented extension point (adl_serializer specialization)
# rather than free-function to_json/from_json in namespace nlohmann -- the
# latter is ODR-fragile across libraries that all do the same. Three
# guards layer the protection:
# * NIM_FFI_NO_OPTIONAL_SERIALIZER : consumer opt-out for conflicts.
# * NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_ : TU-level guard so multiple
# nim-ffi-generated headers don't redefine within one translation unit.
# * nlohmann version check : nlohmann >= 3.12 ships this specialization
# natively, so we skip to avoid an ODR violation against it.
lines.add("#if !defined(NIM_FFI_NO_OPTIONAL_SERIALIZER) \\")
lines.add(" && !defined(NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_) \\")
lines.add(" && (!defined(NLOHMANN_JSON_VERSION_MAJOR) \\")
lines.add(" || (NLOHMANN_JSON_VERSION_MAJOR < 3) \\")
lines.add(" || (NLOHMANN_JSON_VERSION_MAJOR == 3 && NLOHMANN_JSON_VERSION_MINOR < 12))")
lines.add("#define NIM_FFI_OPTIONAL_SERIALIZER_DEFINED_")
lines.add("namespace nlohmann {")
lines.add(" template<typename T>")
lines.add(" void to_json(json& j, const std::optional<T>& opt) {")
lines.add(" if (opt) j = *opt;")
lines.add(" else j = nullptr;")
lines.add(" }")
lines.add("")
lines.add(" template<typename T>")
lines.add(" void from_json(const json& j, std::optional<T>& opt) {")
lines.add(" if (j.is_null()) opt = std::nullopt;")
lines.add(" else opt = j.get<T>();")
lines.add(" }")
lines.add(" struct adl_serializer<std::optional<T>> {")
lines.add(" static void to_json(json& j, const std::optional<T>& opt) {")
lines.add(" if (opt) j = *opt;")
lines.add(" else j = nullptr;")
lines.add(" }")
lines.add(" static void from_json(const json& j, std::optional<T>& opt) {")
lines.add(" if (j.is_null()) opt = std::nullopt;")
lines.add(" else opt = j.get<T>();")
lines.add(" }")
lines.add(" };")
lines.add("}")
lines.add("#endif")
lines.add("")
# ── Types ──────────────────────────────────────────────────────────────────