2018-06-23 17:25:30 +02:00
|
|
|
#include "./binaryserializationcodegenerator.h"
|
|
|
|
|
|
|
|
#include "../lib/binary/serializable.h"
|
|
|
|
|
|
|
|
#include <clang/AST/DeclCXX.h>
|
|
|
|
#include <clang/AST/DeclFriend.h>
|
|
|
|
#include <clang/AST/DeclTemplate.h>
|
2021-06-06 15:06:55 +02:00
|
|
|
#include <clang/AST/Expr.h>
|
|
|
|
#include <clang/AST/RecursiveASTVisitor.h>
|
|
|
|
|
|
|
|
#include <llvm/ADT/APInt.h>
|
2018-06-23 17:25:30 +02:00
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
using namespace std;
|
2019-06-10 22:46:06 +02:00
|
|
|
using namespace CppUtilities;
|
2018-06-23 17:25:30 +02:00
|
|
|
|
|
|
|
namespace ReflectiveRapidJSON {
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Initializes the CLI arguments which are specific to the BinarySerializationCodeGenerator.
|
|
|
|
* \todo Find a more general approach to pass CLI arguments from main() to the particular code generators.
|
|
|
|
*/
|
|
|
|
BinarySerializationCodeGenerator::Options::Options()
|
|
|
|
: additionalClassesArg("binary-classes", '\0', "specifies additional classes to consider for binary (de)serialization", { "class-name" })
|
|
|
|
, visibilityArg("binary-visibility", '\0', "specifies the \"visibility attribute\" for generated functions", { "attribute" })
|
|
|
|
{
|
|
|
|
additionalClassesArg.setRequiredValueCount(Argument::varValueCount);
|
|
|
|
additionalClassesArg.setValueCompletionBehavior(ValueCompletionBehavior::None);
|
2019-06-14 19:16:51 +02:00
|
|
|
visibilityArg.setPreDefinedCompletionValues("CPP_UTILITIES_GENERIC_LIB_EXPORT");
|
2018-06-23 17:25:30 +02:00
|
|
|
}
|
|
|
|
|
2018-06-23 18:04:30 +02:00
|
|
|
BinarySerializationCodeGenerator::BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options)
|
|
|
|
: SerializationCodeGenerator(factory)
|
|
|
|
, m_options(options)
|
2018-06-23 17:25:30 +02:00
|
|
|
{
|
2018-06-23 18:04:30 +02:00
|
|
|
m_qualifiedNameOfRecords = BinarySerializable<void>::qualifiedName;
|
|
|
|
m_qualifiedNameOfAdaptionRecords = AdaptedBinarySerializable<void>::qualifiedName;
|
2018-06-23 17:25:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2021-06-06 15:06:55 +02:00
|
|
|
* \brief Checks whether \a possiblyRelevantClass is actually relevant.
|
2018-06-23 17:25:30 +02:00
|
|
|
*/
|
2021-06-06 15:06:55 +02:00
|
|
|
void BinarySerializationCodeGenerator::computeRelevantClass(RelevantClass &possiblyRelevantClass) const
|
2018-06-23 17:25:30 +02:00
|
|
|
{
|
2021-06-06 15:06:55 +02:00
|
|
|
SerializationCodeGenerator::computeRelevantClass(possiblyRelevantClass);
|
|
|
|
if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
|
|
|
|
return;
|
2018-06-23 17:25:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// consider all classes specified via "--additional-classes" argument relevant
|
|
|
|
if (!m_options.additionalClassesArg.isPresent()) {
|
2021-06-06 15:06:55 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (const char *const className : m_options.additionalClassesArg.values()) {
|
|
|
|
if (className == possiblyRelevantClass.qualifiedName) {
|
|
|
|
possiblyRelevantClass.isRelevant = IsRelevant::Yes;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief The RetrieveIntegerLiteralFromDeclaratorDecl struct is used to traverse a variable declaration to get the integer value.
|
|
|
|
struct RetrieveIntegerLiteralFromDeclaratorDecl : public clang::RecursiveASTVisitor<RetrieveIntegerLiteralFromDeclaratorDecl> {
|
|
|
|
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<const clang::IntegerLiteral *>(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;
|
2018-06-23 17:25:30 +02:00
|
|
|
}
|
2021-06-06 15:06:55 +02:00
|
|
|
auto *const declarator = static_cast<clang::DeclaratorDecl *>(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;
|
|
|
|
}
|
|
|
|
}
|
2018-06-23 17:25:30 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-06 15:06:55 +02:00
|
|
|
return true;
|
|
|
|
}
|
2018-06-23 17:25:30 +02:00
|
|
|
|
2021-06-06 15:06:55 +02:00
|
|
|
/*!
|
|
|
|
* \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 << " ";
|
|
|
|
}
|
2018-06-23 17:25:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Generates pull() and push() helper functions in the ReflectiveRapidJSON::BinaryReflector namespace for the relevant classes.
|
|
|
|
*/
|
2021-06-06 15:06:55 +02:00
|
|
|
void BinarySerializationCodeGenerator::generate(std::ostream &os) const
|
2018-06-23 17:25:30 +02:00
|
|
|
{
|
|
|
|
// initialize source manager to make use of isOnlyIncluded() for skipping records which are only included
|
|
|
|
lazyInitializeSourceManager();
|
|
|
|
|
|
|
|
// find relevant classes
|
|
|
|
const auto relevantClasses = findRelevantClasses();
|
|
|
|
if (relevantClasses.empty()) {
|
|
|
|
return; // nothing to generate
|
|
|
|
}
|
|
|
|
|
|
|
|
// put everything into namespace ReflectiveRapidJSON::BinaryReflector
|
|
|
|
os << "namespace ReflectiveRapidJSON {\n"
|
|
|
|
"namespace BinaryReflector {\n\n";
|
|
|
|
|
|
|
|
// determine visibility attribute
|
|
|
|
const char *visibility = m_options.visibilityArg.firstValue();
|
|
|
|
if (!visibility) {
|
|
|
|
visibility = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
// add push and pull functions for each class, for an example of the resulting
|
|
|
|
// output, see ../lib/tests/binaryserializable.cpp
|
|
|
|
for (const RelevantClass &relevantClass : relevantClasses) {
|
|
|
|
// determine whether private members should be pushed/pulled as well: check whether friend declarations for push/pull present
|
|
|
|
// note: the friend declarations we are looking for are expanded from the REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS macro
|
|
|
|
bool writePrivateMembers = false, readPrivateMembers = false;
|
|
|
|
for (const clang::FriendDecl *const friendDecl : relevantClass.record->friends()) {
|
|
|
|
// get the actual declaration which must be a function
|
|
|
|
const clang::NamedDecl *const actualFriendDecl = friendDecl->getFriendDecl();
|
|
|
|
if (!actualFriendDecl || actualFriendDecl->getKind() != clang::Decl::Kind::Function) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// check whether the friend function matches the push/pull helper function
|
|
|
|
const string friendName(actualFriendDecl->getQualifiedNameAsString());
|
|
|
|
if (friendName == "ReflectiveRapidJSON::BinaryReflector::writeCustomType") {
|
|
|
|
writePrivateMembers = true;
|
|
|
|
}
|
|
|
|
if (friendName == "ReflectiveRapidJSON::BinaryReflector::readCustomType") {
|
|
|
|
readPrivateMembers = true;
|
|
|
|
}
|
|
|
|
if (writePrivateMembers && readPrivateMembers) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// find relevant base classes
|
|
|
|
const vector<const RelevantClass *> relevantBases = findRelevantBaseClasses(relevantClass, relevantClasses);
|
|
|
|
|
|
|
|
// print comment
|
|
|
|
os << "// define code for (de)serializing " << relevantClass.qualifiedName << " objects\n";
|
|
|
|
|
|
|
|
// print writeCustomType method
|
|
|
|
os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName
|
2021-06-06 15:06:55 +02:00
|
|
|
<< ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
|
2022-05-28 18:46:21 +02:00
|
|
|
os << " // write base classes\n";
|
|
|
|
for (const RelevantClass *baseClass : relevantBases) {
|
|
|
|
os << " serializer.write(static_cast<const ::" << baseClass->qualifiedName << " &>(customObject), version);\n";
|
|
|
|
}
|
2021-06-06 15:06:55 +02:00
|
|
|
if (!relevantClass.relevantBase.empty()) {
|
|
|
|
os << " // write version\n"
|
|
|
|
" using V = Versioning<"
|
|
|
|
<< relevantClass.relevantBase
|
|
|
|
<< ">;\n"
|
|
|
|
" if constexpr (V::enabled) {\n"
|
|
|
|
" serializer.writeVariableLengthUIntBE(V::applyDefault(version));\n"
|
|
|
|
" }\n";
|
|
|
|
}
|
2018-06-23 17:25:30 +02:00
|
|
|
os << " // write members\n";
|
2021-06-06 15:06:55 +02:00
|
|
|
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;
|
2018-06-23 17:25:30 +02:00
|
|
|
}
|
2021-06-06 15:06:55 +02:00
|
|
|
|
|
|
|
// skip const members
|
|
|
|
const auto *const field = static_cast<const clang::FieldDecl *>(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;
|
2018-06-23 17:25:30 +02:00
|
|
|
}
|
2021-06-06 15:06:55 +02:00
|
|
|
mt.concludeCondition(os);
|
|
|
|
if (relevantBases.empty() && !mt.membersWritten) {
|
|
|
|
os << " (void)serializer;\n (void)customObject;\n \n(void)version;";
|
2021-03-22 14:16:40 +01:00
|
|
|
}
|
2018-06-23 17:25:30 +02:00
|
|
|
os << "}\n";
|
|
|
|
|
|
|
|
// skip printing the readCustomType method for classes without default constructor because deserializing those is currently not supported
|
|
|
|
if (!relevantClass.record->hasDefaultConstructor()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// print readCustomType method
|
2021-06-06 15:06:55 +02:00
|
|
|
mt = MemberTracking();
|
|
|
|
os << "template <> " << visibility << " BinaryVersion readCustomType<::" << relevantClass.qualifiedName
|
|
|
|
<< ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
|
2022-05-28 18:46:21 +02:00
|
|
|
os << " // read base classes\n";
|
|
|
|
for (const RelevantClass *baseClass : relevantBases) {
|
|
|
|
os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject), version);\n";
|
|
|
|
}
|
2021-06-06 15:06:55 +02:00
|
|
|
if (!relevantClass.relevantBase.empty()) {
|
|
|
|
os << " // read version\n"
|
2021-07-25 19:19:02 +02:00
|
|
|
" using V = Versioning<"
|
2021-06-06 15:06:55 +02:00
|
|
|
<< relevantClass.relevantBase
|
2021-07-25 19:19:02 +02:00
|
|
|
<< ">;\n"
|
|
|
|
" if constexpr (V::enabled) {\n"
|
|
|
|
" V::assertVersion(version = deserializer.readVariableLengthUIntBE(), \""
|
|
|
|
<< relevantClass.qualifiedName
|
|
|
|
<< "\");\n"
|
2021-06-06 15:06:55 +02:00
|
|
|
" }\n";
|
|
|
|
}
|
2018-06-23 17:25:30 +02:00
|
|
|
os << " // read members\n";
|
2021-06-06 15:06:55 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-06-23 17:25:30 +02:00
|
|
|
// skip const members
|
2021-06-06 15:06:55 +02:00
|
|
|
const auto *const field = static_cast<const clang::FieldDecl *>(decl);
|
2018-06-23 17:25:30 +02:00
|
|
|
if (field->getType().isConstant(field->getASTContext())) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-06-06 15:06:55 +02:00
|
|
|
|
|
|
|
// write version markers
|
|
|
|
mt.writeVersionCondition(os);
|
|
|
|
mt.writeExtraPadding(os);
|
|
|
|
|
2018-06-23 17:25:30 +02:00
|
|
|
if (readPrivateMembers || field->getAccess() == clang::AS_public) {
|
2021-06-06 15:06:55 +02:00
|
|
|
os << " deserializer.read(customObject." << field->getName() << ", version);\n";
|
|
|
|
mt.membersWritten = true;
|
2018-06-23 17:25:30 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-06 15:06:55 +02:00
|
|
|
mt.concludeCondition(os);
|
|
|
|
if (relevantBases.empty() && !mt.membersWritten) {
|
2021-03-22 14:16:40 +01:00
|
|
|
os << " (void)deserializer;\n (void)customObject;\n";
|
|
|
|
}
|
2021-06-06 15:06:55 +02:00
|
|
|
os << " return version;\n";
|
2018-06-23 17:25:30 +02:00
|
|
|
os << "}\n\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
// close namespace ReflectiveRapidJSON::BinaryReflector
|
|
|
|
os << "} // namespace BinaryReflector\n"
|
|
|
|
"} // namespace ReflectiveRapidJSON\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace ReflectiveRapidJSON
|