Reflection for RapidJSON 0.0.17
Reflection for serializing/deserializing with RapidJSON
Loading...
Searching...
No Matches
binaryserializationcodegenerator.cpp
Go to the documentation of this file.
3
4#include "../lib/binary/serializable.h"
5
6#include <clang/AST/DeclCXX.h>
7#include <clang/AST/DeclFriend.h>
8#include <clang/AST/DeclTemplate.h>
9#include <clang/AST/Expr.h>
10#include <clang/AST/RecursiveASTVisitor.h>
11
12#include <llvm/ADT/APInt.h>
13
14#include <iostream>
15
16using namespace std;
17using namespace CppUtilities;
18
19namespace ReflectiveRapidJSON {
20
26 : additionalClassesArg("binary-classes", '\0', "specifies additional classes to consider for binary (de)serialization", { "class-name" })
27 , visibilityArg("binary-visibility", '\0', "specifies the \"visibility attribute\" for generated functions", { "attribute" })
28{
29 additionalClassesArg.setRequiredValueCount(Argument::varValueCount);
30 additionalClassesArg.setValueCompletionBehavior(ValueCompletionBehavior::None);
31 visibilityArg.setPreDefinedCompletionValues("CPP_UTILITIES_GENERIC_LIB_EXPORT");
32}
33
36 , m_options(options)
37{
38 m_qualifiedNameOfRecords = BinarySerializable<void>::qualifiedName;
39 m_qualifiedNameOfAdaptionRecords = AdaptedBinarySerializable<void>::qualifiedName;
40}
41
46{
48 if (possiblyRelevantClass.isRelevant != IsRelevant::Maybe) {
49 return;
50 }
51
52 // consider all classes specified via "--additional-classes" argument relevant
53 if (!m_options.additionalClassesArg.isPresent()) {
54 return;
55 }
56 for (const char *const className : m_options.additionalClassesArg.values()) {
57 if (className == possiblyRelevantClass.qualifiedName) {
58 possiblyRelevantClass.isRelevant = IsRelevant::Yes;
59 return;
60 }
61 }
62}
63
65struct RetrieveIntegerLiteralFromDeclaratorDecl : public clang::RecursiveASTVisitor<RetrieveIntegerLiteralFromDeclaratorDecl> {
66 explicit RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx);
67 bool VisitStmt(clang::Stmt *st);
68 const clang::ASTContext &ctx;
69 std::uint64_t res;
70 bool success;
71};
72
80
83{
84 if (st->getStmtClass() != clang::Stmt::IntegerLiteralClass) {
85 return true;
86 }
87 const auto *const integerLiteral = static_cast<const clang::IntegerLiteral *>(st);
88 auto evaluation = clang::Expr::EvalResult();
89 integerLiteral->EvaluateAsInt(evaluation, ctx, clang::Expr::SE_NoSideEffects, true);
90 if (!evaluation.Val.isInt()) {
91 return true;
92 }
93 const auto &asInt = evaluation.Val.getInt();
94 if (asInt.getActiveBits() > 64) {
95 return true;
96 }
97 res = asInt.getZExtValue();
98 success = true;
99 return false;
100}
101
104 bool membersWritten = false, withinCondition = false;
105 BinaryVersion asOfVersion = BinaryVersion(), lastAsOfVersion = BinaryVersion();
106 BinaryVersion untilVersion = BinaryVersion(), lastUntilVersion = BinaryVersion();
107
108 bool checkForVersionMarker(clang::Decl *decl);
109 void concludeCondition(std::ostream &os);
110 void writeVersionCondition(std::ostream &os);
111 void writeExtraPadding(std::ostream &os);
112};
113
119{
120 if (decl->getKind() != clang::Decl::Kind::Var) {
121 return false;
122 }
123 auto *const declarator = static_cast<clang::DeclaratorDecl *>(decl);
124 const auto declarationName = declarator->getName();
125 const auto isAsOfVersion = startsStrRefWith<llvm::StringRef>(declarationName, "rrjAsOfVersion");
126 if (isAsOfVersion || startsStrRefWith<llvm::StringRef>(declarationName, "rrjUntilVersion")) {
127 auto v = RetrieveIntegerLiteralFromDeclaratorDecl(declarator->getASTContext());
128 v.TraverseDecl(declarator);
129 if (v.success) {
130 if (isAsOfVersion) {
131 asOfVersion = v.res;
133 untilVersion = 0;
134 }
135 } else {
136 untilVersion = v.res;
138 asOfVersion = 0;
139 }
140 }
141 }
142 }
143 return true;
144}
145
150{
151 if (withinCondition) {
152 os << " }\n";
153 }
154}
155
160{
162 return;
163 }
168 os << " if (";
169 if (asOfVersion) {
170 os << "version >= " << asOfVersion;
171 if (untilVersion) {
172 os << " && ";
173 }
174 }
175 if (untilVersion) {
176 os << "version <= " << untilVersion;
177 }
178 os << ") {\n";
179 }
180}
181
186{
187 if (withinCondition) {
188 os << " ";
189 }
190}
191
196{
197 // initialize source manager to make use of isOnlyIncluded() for skipping records which are only included
199
200 // find relevant classes
201 const auto relevantClasses = findRelevantClasses();
202 if (relevantClasses.empty()) {
203 return; // nothing to generate
204 }
205
206 // put everything into namespace ReflectiveRapidJSON::BinaryReflector
207 os << "namespace ReflectiveRapidJSON {\n"
208 "namespace BinaryReflector {\n\n";
209
210 // determine visibility attribute
211 const char *visibility = m_options.visibilityArg.firstValue();
212 if (!visibility) {
213 visibility = "";
214 }
215
216 // add push and pull functions for each class, for an example of the resulting
217 // output, see ../lib/tests/binaryserializable.cpp
218 for (const RelevantClass &relevantClass : relevantClasses) {
219 // determine whether private members should be pushed/pulled as well: check whether friend declarations for push/pull present
220 // note: the friend declarations we are looking for are expanded from the REFLECTIVE_RAPIDJSON_ENABLE_PRIVATE_MEMBERS macro
221 bool writePrivateMembers = false, readPrivateMembers = false;
222 for (const clang::FriendDecl *const friendDecl : relevantClass.record->friends()) {
223 // get the actual declaration which must be a function
224 const clang::NamedDecl *const actualFriendDecl = friendDecl->getFriendDecl();
225 if (!actualFriendDecl || actualFriendDecl->getKind() != clang::Decl::Kind::Function) {
226 continue;
227 }
228 // check whether the friend function matches the push/pull helper function
229 const string friendName(actualFriendDecl->getQualifiedNameAsString());
230 if (friendName == "ReflectiveRapidJSON::BinaryReflector::writeCustomType") {
231 writePrivateMembers = true;
232 }
233 if (friendName == "ReflectiveRapidJSON::BinaryReflector::readCustomType") {
234 readPrivateMembers = true;
235 }
236 if (writePrivateMembers && readPrivateMembers) {
237 break;
238 }
239 }
240
241 // find relevant base classes
242 const vector<const RelevantClass *> relevantBases = findRelevantBaseClasses(relevantClass, relevantClasses);
243
244 // print comment
245 os << "// define code for (de)serializing " << relevantClass.qualifiedName << " objects\n";
246
247 // print writeCustomType method
248 os << "template <> " << visibility << " void writeCustomType<::" << relevantClass.qualifiedName
249 << ">(BinarySerializer &serializer, const ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
250 os << " // write base classes\n";
251 for (const RelevantClass *baseClass : relevantBases) {
252 os << " serializer.write(static_cast<const ::" << baseClass->qualifiedName << " &>(customObject), version);\n";
253 }
254 if (!relevantClass.relevantBase.empty()) {
255 os << " // write version\n"
256 " using V = Versioning<"
257 << relevantClass.relevantBase
258 << ">;\n"
259 " if constexpr (V::enabled) {\n"
260 " serializer.writeVariableLengthUIntBE(V::applyDefault(version));\n"
261 " }\n";
262 }
263 os << " // write members\n";
264 auto mt = MemberTracking();
265 for (clang::Decl *const decl : relevantClass.record->decls()) {
266 // check static member variables for version markers
267 if (mt.checkForVersionMarker(decl)) {
268 continue;
269 }
270
271 // skip all further declarations but fields
272 if (decl->getKind() != clang::Decl::Kind::Field) {
273 continue;
274 }
275
276 // skip const members
277 const auto *const field = static_cast<const clang::FieldDecl *>(decl);
278 if (field->getType().isConstant(field->getASTContext())) {
279 continue;
280 }
281
282 // skip private members conditionally
283 if (!writePrivateMembers && field->getAccess() != clang::AS_public) {
284 continue;
285 }
286
287 // write version markers
288 mt.writeVersionCondition(os);
289 mt.writeExtraPadding(os);
290
291 // write actual code for serialization
292 os << " serializer.write(customObject." << field->getName() << ", version);\n";
293 mt.membersWritten = true;
294 }
295 mt.concludeCondition(os);
296 if (relevantBases.empty() && !mt.membersWritten) {
297 os << " (void)serializer;\n (void)customObject;\n \n(void)version;";
298 }
299 os << "}\n";
300
301 // skip printing the readCustomType method for classes without default constructor because deserializing those is currently not supported
302 if (!relevantClass.record->hasDefaultConstructor()) {
303 continue;
304 }
305
306 // print readCustomType method
307 mt = MemberTracking();
308 os << "template <> " << visibility << " BinaryVersion readCustomType<::" << relevantClass.qualifiedName
309 << ">(BinaryDeserializer &deserializer, ::" << relevantClass.qualifiedName << " &customObject, BinaryVersion version)\n{\n";
310 os << " // read base classes\n";
311 for (const RelevantClass *baseClass : relevantBases) {
312 os << " deserializer.read(static_cast<::" << baseClass->qualifiedName << " &>(customObject), version);\n";
313 }
314 if (!relevantClass.relevantBase.empty()) {
315 os << " // read version\n"
316 " using V = Versioning<"
317 << relevantClass.relevantBase
318 << ">;\n"
319 " if constexpr (V::enabled) {\n"
320 " V::assertVersion(version = deserializer.readVariableLengthUIntBE(), \""
321 << relevantClass.qualifiedName
322 << "\");\n"
323 " }\n";
324 }
325 os << " // read members\n";
326 for (clang::Decl *const decl : relevantClass.record->decls()) {
327 // check static member variables for version markers
328 if (mt.checkForVersionMarker(decl)) {
329 continue;
330 }
331
332 // skip all further declarations but fields
333 if (decl->getKind() != clang::Decl::Kind::Field) {
334 continue;
335 }
336
337 // skip const members
338 const auto *const field = static_cast<const clang::FieldDecl *>(decl);
339 if (field->getType().isConstant(field->getASTContext())) {
340 continue;
341 }
342
343 // write version markers
344 mt.writeVersionCondition(os);
345 mt.writeExtraPadding(os);
346
347 if (readPrivateMembers || field->getAccess() == clang::AS_public) {
348 os << " deserializer.read(customObject." << field->getName() << ", version);\n";
349 mt.membersWritten = true;
350 }
351 }
352 mt.concludeCondition(os);
353 if (relevantBases.empty() && !mt.membersWritten) {
354 os << " (void)deserializer;\n (void)customObject;\n";
355 }
356 os << " return version;\n";
357 os << "}\n\n";
358 }
359
360 // close namespace ReflectiveRapidJSON::BinaryReflector
361 os << "} // namespace BinaryReflector\n"
362 "} // namespace ReflectiveRapidJSON\n";
363}
364
365} // namespace ReflectiveRapidJSON
void computeRelevantClass(RelevantClass &possiblyRelevantClass) const override
Checks whether possiblyRelevantClass is actually relevant.
void generate(std::ostream &os) const override
Generates pull() and push() helper functions in the ReflectiveRapidJSON::BinaryReflector namespace fo...
BinarySerializationCodeGenerator(CodeFactory &factory, const Options &options)
The CodeFactory class produces additional (reflection) code for a specified list of C++ source files.
Definition codefactory.h:27
void lazyInitializeSourceManager() const
Lazy initializes the source manager.
static std::vector< const RelevantClass * > findRelevantBaseClasses(const RelevantClass &relevantClass, const std::vector< RelevantClass > &relevantBases)
virtual void computeRelevantClass(RelevantClass &possiblyRelevantClass) const
bool startsStrRefWith(T stringRef1, T stringRef2)
Tests whether an llvm::StringRef starts with another llvm::StringRef.
Options()
Initializes the CLI arguments which are specific to the BinarySerializationCodeGenerator.
The MemberTracking struct is an internal helper for BinarySerializationCodeGenerator::generate().
bool checkForVersionMarker(clang::Decl *decl)
Returns whether delc is a static member variable and processes special static member variables for ve...
void concludeCondition(std::ostream &os)
Concludes an unfinished version condition if-block.
void writeVersionCondition(std::ostream &os)
Starts a new version condition if-block if versioning parameters have changed.
void writeExtraPadding(std::ostream &os)
Writes extra padding (if within a version condition).
RetrieveIntegerLiteralFromDeclaratorDecl(const clang::ASTContext &ctx)
Constructs a new instance for the specified AST context.
bool VisitStmt(clang::Stmt *st)
Reads the integer value of st for integer literals.