From db0082c32548dc7bce30771307706b6cf942df5f Mon Sep 17 00:00:00 2001 From: Martchus Date: Sun, 6 Jun 2021 19:24:40 +0200 Subject: [PATCH] WIP: Versioning --- .../binaryserializationcodegenerator.cpp | 200 ++++++++++++++++-- generator/tests/morestructs.h | 2 + lib/binary/reflector-boosthana.h | 3 +- lib/binary/reflector-chronoutilities.h | 8 +- lib/binary/reflector.h | 10 +- lib/binary/serializable.h | 6 +- lib/tests/binaryreflector.cpp | 7 +- lib/versioning.h | 12 +- 8 files changed, 220 insertions(+), 28 deletions(-) diff --git a/generator/binaryserializationcodegenerator.cpp b/generator/binaryserializationcodegenerator.cpp index a527104..216c245 100644 --- a/generator/binaryserializationcodegenerator.cpp +++ b/generator/binaryserializationcodegenerator.cpp @@ -5,6 +5,10 @@ #include #include #include +#include +#include + +#include #include @@ -61,10 +65,138 @@ string BinarySerializationCodeGenerator::qualifiedNameIfRelevant(clang::CXXRecor return string(); } +/// \brief The RetrieveIntegerLiteralFromDeclaratorDecl struct is used to traverse a variable declaration to get the integer value. +struct RetrieveIntegerLiteralFromDeclaratorDecl : public clang::RecursiveASTVisitor { + explicit RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx); + bool VisitStmt(clang::Stmt *st); + const clang::ASTContext &ctx; + std::uint64_t res; + bool success; +}; + +/// \brief Constructs a new instance for the specified AST context. +RetrieveIntegerLiteralFromDeclaratorDecl::RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx) + : ctx(ctx) + , res(0) + , success(false) +{ +} + +/// \brief Reads the integer value of \a st for integer literals. +bool RetrieveIntegerLiteralFromDeclaratorDecl::VisitStmt(clang::Stmt *st) +{ + if (st->getStmtClass() != clang::Stmt::IntegerLiteralClass) { + return true; + } + const auto *const integerLiteral = static_cast(st); + auto evaluation = clang::Expr::EvalResult(); + integerLiteral->EvaluateAsInt(evaluation, ctx, clang::Expr::SE_NoSideEffects, true); + if (!evaluation.Val.isInt()) { + return true; + } + const auto &asInt = evaluation.Val.getInt(); + if (asInt.getActiveBits() > 64) { + return true; + } + res = asInt.getZExtValue(); + success = true; + return false; +} + +/// \brief The MemberTracking struct is an internal helper for BinarySerializationCodeGenerator::generate(). +struct MemberTracking { + bool membersWritten = false, withinCondition = false; + BinaryVersion asOfVersion = BinaryVersion(), lastAsOfVersion = BinaryVersion(); + BinaryVersion untilVersion = BinaryVersion(), lastUntilVersion = BinaryVersion(); + + bool checkForVersionMarker(clang::Decl *decl); + void concludeCondition(std::ostream &os); + void writeVersionCondition(std::ostream &os); + void writeExtraPadding(std::ostream &os); +}; + +/*! + * \brief Returns whether \a delc is a static member variable and processes special static member variables + * for versioning. + */ +bool MemberTracking::checkForVersionMarker(clang::Decl *decl) +{ + if (decl->getKind() != clang::Decl::Kind::Var) { + return false; + } + auto *const declarator = static_cast(decl); + const auto declarationName = declarator->getName(); + const auto isAsOfVersion = declarationName.startswith("rrjAsOfVersion"); + if (isAsOfVersion || declarationName.startswith("rrjUntilVersion")) { + auto v = RetrieveIntegerLiteralFromDeclaratorDecl(declarator->getASTContext()); + v.TraverseDecl(declarator); + if (v.success) { + if (isAsOfVersion) { + asOfVersion = v.res; + if (asOfVersion > untilVersion) { + untilVersion = 0; + } + } else { + untilVersion = v.res; + if (untilVersion < asOfVersion) { + asOfVersion = 0; + } + } + } + } + return true; +} + +/*! + * \brief Concludes an unfinished version condition if-block. + */ +void MemberTracking::concludeCondition(std::ostream &os) +{ + if (withinCondition) { + os << " }\n"; + } +} + +/*! + * \brief Starts a new version condition if-block if versioning parameters have changed. + */ +void MemberTracking::writeVersionCondition(std::ostream &os) +{ + if (asOfVersion == lastAsOfVersion && untilVersion == lastUntilVersion) { + return; + } + concludeCondition(os); + lastAsOfVersion = asOfVersion; + lastUntilVersion = untilVersion; + if ((withinCondition = asOfVersion || untilVersion)) { + os << " if ("; + if (asOfVersion) { + os << "version >= " << asOfVersion; + if (untilVersion) { + os << " && "; + } + } + if (untilVersion) { + os << "version <= " << untilVersion; + } + os << ") {\n"; + } +} + +/*! + * \brief Writes extra padding (if within a version condition). + */ +void MemberTracking::writeExtraPadding(std::ostream &os) +{ + if (withinCondition) { + os << " "; + } +} + /*! * \brief Generates pull() and push() helper functions in the ReflectiveRapidJSON::BinaryReflector namespace for the relevant classes. */ -void BinarySerializationCodeGenerator::generate(ostream &os) const +void BinarySerializationCodeGenerator::generate(std::ostream &os) const { // initialize source manager to make use of isOnlyIncluded() for skipping records which are only included lazyInitializeSourceManager(); @@ -119,20 +251,45 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const // print writeCustomType method os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName << ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName - << " &customObject)\n{\n" + << " &customObject, BinaryVersion version)\n{\n" " // write base classes\n"; for (const RelevantClass *baseClass : relevantBases) { - os << " serializer.write(static_castqualifiedName << " &>(customObject));\n"; + os << " serializer.write(static_castqualifiedName << " &>(customObject), version);\n"; } os << " // write members\n"; - auto membersWritten = false; - for (const clang::FieldDecl *field : relevantClass.record->fields()) { - if (writePrivateMembers || field->getAccess() == clang::AS_public) { - os << " serializer.write(customObject." << field->getName() << ");\n"; - membersWritten = true; + auto mt = MemberTracking(); + for (clang::Decl *const decl : relevantClass.record->decls()) { + // check static member variables for version markers + if (mt.checkForVersionMarker(decl)) { + continue; } + + // skip all further declarations but fields + if (decl->getKind() != clang::Decl::Kind::Field) { + continue; + } + + // skip const members + const auto *const field = static_cast(decl); + if (field->getType().isConstant(field->getASTContext())) { + continue; + } + + // skip private members conditionally + if (!writePrivateMembers && field->getAccess() != clang::AS_public) { + continue; + } + + // write version markers + mt.writeVersionCondition(os); + mt.writeExtraPadding(os); + + // write actual code for serialization + os << " serializer.write(customObject." << field->getName() << ", version);\n"; + mt.membersWritten = true; } - if (relevantBases.empty() && !membersWritten) { + mt.concludeCondition(os); + if (relevantBases.empty() && !mt.membersWritten) { os << " (void)serializer;\n (void)customObject;\n"; } os << "}\n"; @@ -143,6 +300,7 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const } // print readCustomType method + mt = MemberTracking(); os << "template <> " << visibility << " void readCustomType<::" << relevantClass.qualifiedName << ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName << " &customObject)\n{\n" @@ -151,18 +309,34 @@ void BinarySerializationCodeGenerator::generate(ostream &os) const os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject));\n"; } os << " // read members\n"; - auto membersRead = false; - for (const clang::FieldDecl *field : relevantClass.record->fields()) { + for (clang::Decl *const decl : relevantClass.record->decls()) { + // check static member variables for version markers + if (mt.checkForVersionMarker(decl)) { + continue; + } + + // skip all further declarations but fields + if (decl->getKind() != clang::Decl::Kind::Field) { + continue; + } + // skip const members + const auto *const field = static_cast(decl); if (field->getType().isConstant(field->getASTContext())) { continue; } + + // write version markers + mt.writeVersionCondition(os); + mt.writeExtraPadding(os); + if (readPrivateMembers || field->getAccess() == clang::AS_public) { os << " deserializer.read(customObject." << field->getName() << ");\n"; - membersRead = true; + mt.membersWritten = true; } } - if (relevantBases.empty() && !membersRead) { + mt.concludeCondition(os); + if (relevantBases.empty() && !mt.membersWritten) { os << " (void)deserializer;\n (void)customObject;\n"; } os << "}\n\n"; diff --git a/generator/tests/morestructs.h b/generator/tests/morestructs.h index 14cf3b6..1b5f529 100644 --- a/generator/tests/morestructs.h +++ b/generator/tests/morestructs.h @@ -73,6 +73,7 @@ struct PointerStruct : public BinarySerializable { /*! * \brief The PointerStruct struct is used to test the behavior of the binary (de)serialization with smart pointer. */ +// clang-format off struct VersionedStruct : public BinarySerializable { std::uint32_t a, b; @@ -82,5 +83,6 @@ until_version(2): as_of_version(3): std::uint32_t e, f; }; +// clang-format on #endif // REFLECTIVE_RAPIDJSON_TESTS_MORE_STRUCTS_H diff --git a/lib/binary/reflector-boosthana.h b/lib/binary/reflector-boosthana.h index fb54f05..4b455cc 100644 --- a/lib/binary/reflector-boosthana.h +++ b/lib/binary/reflector-boosthana.h @@ -30,7 +30,8 @@ template > *> void readCustom boost::hana::keys(customType), [&deserializer, &customType](auto key) { deserializer.read(boost::hana::at_key(customType, key)); }); } -template > *> void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version) +template > *> +void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version) { boost::hana::for_each( boost::hana::keys(customType), [&serializer, &customType](auto key) { serializer.write(boost::hana::at_key(customType, key)); }); diff --git a/lib/binary/reflector-chronoutilities.h b/lib/binary/reflector-chronoutilities.h index 79c911e..00f153a 100644 --- a/lib/binary/reflector-chronoutilities.h +++ b/lib/binary/reflector-chronoutilities.h @@ -21,8 +21,10 @@ template <> inline void readCustomType(BinaryDeserialize deserializer.read(dateTime.ticks()); } -template <> inline void writeCustomType(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime) +template <> +inline void writeCustomType(BinarySerializer &serializer, const CppUtilities::DateTime &dateTime, BinaryVersion version) { + CPP_UTILITIES_UNUSED(version) serializer.write(dateTime.totalTicks()); } @@ -31,8 +33,10 @@ template <> inline void readCustomType(BinaryDeserialize deserializer.read(timeSpan.ticks()); } -template <> inline void writeCustomType(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan) +template <> +inline void writeCustomType(BinarySerializer &serializer, const CppUtilities::TimeSpan &timeSpan, BinaryVersion version) { + CPP_UTILITIES_UNUSED(version) serializer.write(timeSpan.totalTicks()); } diff --git a/lib/binary/reflector.h b/lib/binary/reflector.h index 312ff37..bfc72af 100644 --- a/lib/binary/reflector.h +++ b/lib/binary/reflector.h @@ -53,7 +53,8 @@ class BinaryDeserializer; class BinarySerializer; template > * = nullptr> void readCustomType(BinaryDeserializer &deserializer, Type &customType); -template > * = nullptr> void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version = 0); +template > * = nullptr> +void writeCustomType(BinarySerializer &serializer, const Type &customType, BinaryVersion version = 0); class BinaryDeserializer : public CppUtilities::BinaryReader { friend class ::BinaryReflectorTests; @@ -92,6 +93,7 @@ public: template , Traits::HasSize> * = nullptr> void write(const Type &iteratable); template > * = nullptr> void write(const Type &enumValue); template > * = nullptr> void write(const Type &variant); + template > * = nullptr> void write(const Type &builtInType, BinaryVersion version); template > * = nullptr> void write(const Type &customType, BinaryVersion version = 0); private: @@ -288,6 +290,12 @@ template > *> void BinarySeriali variant); } +template > *> void BinarySerializer::write(const Type &builtInType, BinaryVersion version) +{ + CPP_UTILITIES_UNUSED(version) + write(builtInType); +} + template > *> void BinarySerializer::write(const Type &customType, BinaryVersion version) { writeCustomType(*this, customType, version); diff --git a/lib/binary/serializable.h b/lib/binary/serializable.h index c923b54..d67f8ed 100644 --- a/lib/binary/serializable.h +++ b/lib/binary/serializable.h @@ -26,12 +26,14 @@ template struct BinarySerializable static constexpr auto defaultSerializeVersion = defaultVersion; }; -template inline void BinarySerializable::toBinary(std::ostream &outputStream) const +template +inline void BinarySerializable::toBinary(std::ostream &outputStream) const { BinaryReflector::BinarySerializer(&outputStream).write(static_cast(*this), defaultVersion); } -template inline void BinarySerializable::restoreFromBinary(std::istream &inputStream) +template +inline void BinarySerializable::restoreFromBinary(std::istream &inputStream) { BinaryReflector::BinaryDeserializer(&inputStream).read(static_cast(*this)); } diff --git a/lib/tests/binaryreflector.cpp b/lib/tests/binaryreflector.cpp index e9258f8..d9421ed 100644 --- a/lib/tests/binaryreflector.cpp +++ b/lib/tests/binaryreflector.cpp @@ -94,7 +94,7 @@ template <> void readCustomType(BinaryDeserializer &deserializ deserializer.read(customType.dateTime); } -template <> void writeCustomType(BinarySerializer &serializer, const TestObjectBinary &customType) +template <> void writeCustomType(BinarySerializer &serializer, const TestObjectBinary &customType, BinaryVersion version) { serializer.write(customType.number); serializer.write(customType.number2); @@ -119,7 +119,7 @@ template <> void readCustomType(BinaryDeserializer &deserial deserializer.read(customType.testObjects); } -template <> void writeCustomType(BinarySerializer &serializer, const NestingArrayBinary &customType) +template <> void writeCustomType(BinarySerializer &serializer, const NestingArrayBinary &customType, BinaryVersion version) { serializer.write(customType.name); serializer.write(customType.testObjects); @@ -132,7 +132,8 @@ template <> void readCustomType(BinaryDeserializer &de deserializer.read(customType.yetAnotherVariant); } -template <> void writeCustomType(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType) +template <> +void writeCustomType(BinarySerializer &serializer, const ObjectWithVariantsBinary &customType, BinaryVersion version) { serializer.write(customType.someVariant); serializer.write(customType.anotherVariant); diff --git a/lib/versioning.h b/lib/versioning.h index 13019ae..6e7cd96 100644 --- a/lib/versioning.h +++ b/lib/versioning.h @@ -6,12 +6,12 @@ namespace ReflectiveRapidJSON { #ifdef REFLECTIVE_RAPIDJSON_GENERATOR #define REFLECTIVE_RAPIDJSON_CAT_1(a, b) a##b #define REFLECTIVE_RAPIDJSON_CAT_2(a, b) REFLECTIVE_RAPIDJSON_CAT_1(a, b) -#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) \ - constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjAsOfVersion, __COUNTER__) = version; \ - public -#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) \ - constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjUntilVersion, __COUNTER__) = version; \ - public +#define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) \ + static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjAsOfVersion, __COUNTER__) = version; \ +public +#define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) \ + static constexpr std::size_t REFLECTIVE_RAPIDJSON_CAT_2(rrjUntilVersion, __COUNTER__) = version; \ +public #else #define REFLECTIVE_RAPIDJSON_AS_OF_VERSION(version) public #define REFLECTIVE_RAPIDJSON_UNTIL_VERSION(version) public