1 /*
2 * Copyright 2014 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // independent from idl_parser, since this code is not needed for most clients
18 #include "idl_gen_fbs.h"
19
20 #include <unordered_map>
21 #include <utility>
22 #include <vector>
23
24 #include "flatbuffers/code_generator.h"
25 #include "flatbuffers/code_generators.h"
26 #include "flatbuffers/flatbuffers.h"
27 #include "flatbuffers/idl.h"
28 #include "flatbuffers/util.h"
29
30 namespace flatbuffers {
31 namespace {
32
GenType(const Type & type,bool underlying=false)33 static std::string GenType(const Type &type, bool underlying = false) {
34 switch (type.base_type) {
35 case BASE_TYPE_STRUCT:
36 return type.struct_def->defined_namespace->GetFullyQualifiedName(
37 type.struct_def->name);
38 case BASE_TYPE_VECTOR: return "[" + GenType(type.VectorType()) + "]";
39 default:
40 if (type.enum_def && !underlying) {
41 return type.enum_def->defined_namespace->GetFullyQualifiedName(
42 type.enum_def->name);
43 } else {
44 return TypeName(type.base_type);
45 }
46 }
47 }
48
HasFieldWithId(const std::vector<FieldDef * > & fields)49 static bool HasFieldWithId(const std::vector<FieldDef *> &fields) {
50 static const std::string ID = "id";
51
52 for (const auto *field : fields) {
53 const auto *id_attribute = field->attributes.Lookup(ID);
54 if (id_attribute != nullptr && !id_attribute->constant.empty()) {
55 return true;
56 }
57 }
58 return false;
59 }
60
HasNonPositiveFieldId(const std::vector<FieldDef * > & fields)61 static bool HasNonPositiveFieldId(const std::vector<FieldDef *> &fields) {
62 static const std::string ID = "id";
63
64 for (const auto *field : fields) {
65 const auto *id_attribute = field->attributes.Lookup(ID);
66 if (id_attribute != nullptr && !id_attribute->constant.empty()) {
67 voffset_t proto_id = 0;
68 bool done = StringToNumber(id_attribute->constant.c_str(), &proto_id);
69 if (!done) { return true; }
70 }
71 }
72 return false;
73 }
74
HasFieldIdFromReservedIds(const std::vector<FieldDef * > & fields,const std::vector<voffset_t> & reserved_ids)75 static bool HasFieldIdFromReservedIds(
76 const std::vector<FieldDef *> &fields,
77 const std::vector<voffset_t> &reserved_ids) {
78 static const std::string ID = "id";
79
80 for (const auto *field : fields) {
81 const auto *id_attribute = field->attributes.Lookup(ID);
82 if (id_attribute != nullptr && !id_attribute->constant.empty()) {
83 voffset_t proto_id = 0;
84 bool done = StringToNumber(id_attribute->constant.c_str(), &proto_id);
85 if (!done) { return true; }
86 auto id_it =
87 std::find(std::begin(reserved_ids), std::end(reserved_ids), proto_id);
88 if (id_it != reserved_ids.end()) { return true; }
89 }
90 }
91 return false;
92 }
93
ExtractProtobufIds(const std::vector<FieldDef * > & fields)94 static std::vector<voffset_t> ExtractProtobufIds(
95 const std::vector<FieldDef *> &fields) {
96 static const std::string ID = "id";
97 std::vector<voffset_t> used_proto_ids;
98 for (const auto *field : fields) {
99 const auto *id_attribute = field->attributes.Lookup(ID);
100 if (id_attribute != nullptr && !id_attribute->constant.empty()) {
101 voffset_t proto_id = 0;
102 bool done = StringToNumber(id_attribute->constant.c_str(), &proto_id);
103 if (done) { used_proto_ids.push_back(proto_id); }
104 }
105 }
106
107 return used_proto_ids;
108 }
109
HasTwiceUsedId(const std::vector<FieldDef * > & fields)110 static bool HasTwiceUsedId(const std::vector<FieldDef *> &fields) {
111 std::vector<voffset_t> used_proto_ids = ExtractProtobufIds(fields);
112 std::sort(std::begin(used_proto_ids), std::end(used_proto_ids));
113 for (auto it = std::next(std::begin(used_proto_ids));
114 it != std::end(used_proto_ids); it++) {
115 if (*it == *std::prev(it)) { return true; }
116 }
117
118 return false;
119 }
120
HasGapInProtoId(const std::vector<FieldDef * > & fields)121 static bool HasGapInProtoId(const std::vector<FieldDef *> &fields) {
122 std::vector<voffset_t> used_proto_ids = ExtractProtobufIds(fields);
123 std::sort(std::begin(used_proto_ids), std::end(used_proto_ids));
124 for (auto it = std::next(std::begin(used_proto_ids));
125 it != std::end(used_proto_ids); it++) {
126 if (*it != *std::prev(it) + 1) { return true; }
127 }
128
129 return false;
130 }
131
ProtobufIdSanityCheck(const StructDef & struct_def,IDLOptions::ProtoIdGapAction gap_action,bool no_log=false)132 static bool ProtobufIdSanityCheck(const StructDef &struct_def,
133 IDLOptions::ProtoIdGapAction gap_action,
134 bool no_log = false) {
135 const auto &fields = struct_def.fields.vec;
136 if (HasNonPositiveFieldId(fields)) {
137 // TODO: Use LogCompilerWarn
138 if (!no_log) {
139 fprintf(stderr, "Field id in struct %s has a non positive number value\n",
140 struct_def.name.c_str());
141 }
142 return false;
143 }
144
145 if (HasTwiceUsedId(fields)) {
146 // TODO: Use LogCompilerWarn
147 if (!no_log) {
148 fprintf(stderr, "Fields in struct %s have used an id twice\n",
149 struct_def.name.c_str());
150 }
151 return false;
152 }
153
154 if (HasFieldIdFromReservedIds(fields, struct_def.reserved_ids)) {
155 // TODO: Use LogCompilerWarn
156 if (!no_log) {
157 fprintf(stderr, "Fields in struct %s use id from reserved ids\n",
158 struct_def.name.c_str());
159 }
160 return false;
161 }
162
163 if (gap_action != IDLOptions::ProtoIdGapAction::NO_OP) {
164 if (HasGapInProtoId(fields)) {
165 // TODO: Use LogCompilerWarn
166 if (!no_log) {
167 fprintf(stderr, "Fields in struct %s have gap between ids\n",
168 struct_def.name.c_str());
169 }
170 if (gap_action == IDLOptions::ProtoIdGapAction::ERROR) { return false; }
171 }
172 }
173
174 return true;
175 }
176
177 struct ProtobufToFbsIdMap {
178 using FieldName = std::string;
179 using FieldID = voffset_t;
180 using FieldNameToIdMap = std::unordered_map<FieldName, FieldID>;
181
182 FieldNameToIdMap field_to_id;
183 bool successful = false;
184 };
185
MapProtoIdsToFieldsId(const StructDef & struct_def,IDLOptions::ProtoIdGapAction gap_action,bool no_log)186 static ProtobufToFbsIdMap MapProtoIdsToFieldsId(
187 const StructDef &struct_def, IDLOptions::ProtoIdGapAction gap_action,
188 bool no_log) {
189 const auto &fields = struct_def.fields.vec;
190
191 if (!HasFieldWithId(fields)) {
192 ProtobufToFbsIdMap result;
193 result.successful = true;
194 return result;
195 }
196
197 if (!ProtobufIdSanityCheck(struct_def, gap_action, no_log)) { return {}; }
198
199 static constexpr int UNION_ID = -1;
200 using ProtoIdFieldNamePair = std::pair<int, std::string>;
201 std::vector<ProtoIdFieldNamePair> proto_ids;
202
203 for (const auto *field : fields) {
204 const auto *id_attribute = field->attributes.Lookup("id");
205 if (id_attribute != nullptr) {
206 // When we have union but do not use union flag to keep them
207 if (id_attribute->constant.empty() &&
208 field->value.type.base_type == BASE_TYPE_UNION) {
209 proto_ids.emplace_back(UNION_ID, field->name);
210 } else {
211 voffset_t proto_id = 0;
212 StringToNumber(id_attribute->constant.c_str(), &proto_id);
213 proto_ids.emplace_back(proto_id, field->name);
214 }
215 } else {
216 // TODO: Use LogCompilerWarn
217 if (!no_log) {
218 fprintf(stderr, "Fields id in struct %s is missing\n",
219 struct_def.name.c_str());
220 }
221 return {};
222 }
223 }
224
225 std::sort(
226 std::begin(proto_ids), std::end(proto_ids),
227 [](const ProtoIdFieldNamePair &rhs, const ProtoIdFieldNamePair &lhs) {
228 return rhs.first < lhs.first;
229 });
230 struct ProtobufToFbsIdMap proto_to_fbs;
231
232 voffset_t id = 0;
233 for (const auto &element : proto_ids) {
234 if (element.first == UNION_ID) { id++; }
235 proto_to_fbs.field_to_id.emplace(element.second, id++);
236 }
237 proto_to_fbs.successful = true;
238 return proto_to_fbs;
239 }
240
GenNameSpace(const Namespace & name_space,std::string * _schema,const Namespace ** last_namespace)241 static void GenNameSpace(const Namespace &name_space, std::string *_schema,
242 const Namespace **last_namespace) {
243 if (*last_namespace == &name_space) return;
244 *last_namespace = &name_space;
245 auto &schema = *_schema;
246 schema += "namespace ";
247 for (auto it = name_space.components.begin();
248 it != name_space.components.end(); ++it) {
249 if (it != name_space.components.begin()) schema += ".";
250 schema += *it;
251 }
252 schema += ";\n\n";
253 }
254
255 // Generate a flatbuffer schema from the Parser's internal representation.
GenerateFBS(const Parser & parser,const std::string & file_name,bool no_log=false)256 static std::string GenerateFBS(const Parser &parser,
257 const std::string &file_name,
258 bool no_log = false) {
259 // Proto namespaces may clash with table names, escape the ones that were
260 // generated from a table:
261 for (auto it = parser.namespaces_.begin(); it != parser.namespaces_.end();
262 ++it) {
263 auto &ns = **it;
264 for (size_t i = 0; i < ns.from_table; i++) {
265 ns.components[ns.components.size() - 1 - i] += "_";
266 }
267
268 if (parser.opts.proto_mode && !parser.opts.proto_namespace_suffix.empty()) {
269 // Since we know that all these namespaces come from a .proto, and all are
270 // being converted, we can simply apply this suffix to all of them.
271 ns.components.insert(ns.components.end() - ns.from_table,
272 parser.opts.proto_namespace_suffix);
273 }
274 }
275
276 std::string schema;
277 schema += "// Generated from " + file_name + ".proto\n\n";
278 if (parser.opts.include_dependence_headers) {
279 // clang-format off
280 int num_includes = 0;
281 for (auto it = parser.included_files_.begin();
282 it != parser.included_files_.end(); ++it) {
283 if (it->second.empty()) {
284 continue;
285 }
286 std::string basename;
287 if(parser.opts.keep_prefix) {
288 basename = flatbuffers::StripExtension(it->second);
289 } else {
290 basename = flatbuffers::StripPath(
291 flatbuffers::StripExtension(it->second));
292 }
293 schema += "include \"" + basename + ".fbs\";\n";
294 num_includes++;
295 }
296 if (num_includes) schema += "\n";
297 // clang-format on
298 }
299
300 // Generate code for all the enum declarations.
301 const Namespace *last_namespace = nullptr;
302 for (auto enum_def_it = parser.enums_.vec.begin();
303 enum_def_it != parser.enums_.vec.end(); ++enum_def_it) {
304 EnumDef &enum_def = **enum_def_it;
305 if (parser.opts.include_dependence_headers && enum_def.generated) {
306 continue;
307 }
308 GenNameSpace(*enum_def.defined_namespace, &schema, &last_namespace);
309 GenComment(enum_def.doc_comment, &schema, nullptr);
310 if (enum_def.is_union) {
311 schema += "union " + enum_def.name;
312 } else {
313 schema += "enum " + enum_def.name + " : ";
314 }
315
316 schema += GenType(enum_def.underlying_type, true) + " {\n";
317
318 for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) {
319 auto &ev = **it;
320 GenComment(ev.doc_comment, &schema, nullptr, " ");
321 if (enum_def.is_union) {
322 schema += " " + GenType(ev.union_type) + ",\n";
323 } else {
324 schema += " " + ev.name + " = " + enum_def.ToString(ev) + ",\n";
325 }
326 }
327 schema += "}\n\n";
328 }
329 // Generate code for all structs/tables.
330 for (auto it = parser.structs_.vec.begin(); it != parser.structs_.vec.end();
331 ++it) {
332 StructDef &struct_def = **it;
333 const auto proto_fbs_ids = MapProtoIdsToFieldsId(
334 struct_def, parser.opts.proto_id_gap_action, no_log);
335 if (!proto_fbs_ids.successful) { return {}; }
336
337 if (parser.opts.include_dependence_headers && struct_def.generated) {
338 continue;
339 }
340
341 GenNameSpace(*struct_def.defined_namespace, &schema, &last_namespace);
342 GenComment(struct_def.doc_comment, &schema, nullptr);
343 schema += "table " + struct_def.name + " {\n";
344 for (auto field_it = struct_def.fields.vec.begin();
345 field_it != struct_def.fields.vec.end(); ++field_it) {
346 auto &field = **field_it;
347 if (field.value.type.base_type != BASE_TYPE_UTYPE) {
348 GenComment(field.doc_comment, &schema, nullptr, " ");
349 schema += " " + field.name + ":" + GenType(field.value.type);
350 if (field.value.constant != "0") schema += " = " + field.value.constant;
351 std::vector<std::string> attributes;
352 if (field.IsRequired()) attributes.push_back("required");
353 if (field.key) attributes.push_back("key");
354
355 if (parser.opts.keep_proto_id) {
356 auto it = proto_fbs_ids.field_to_id.find(field.name);
357 if (it != proto_fbs_ids.field_to_id.end()) {
358 attributes.push_back("id: " + NumToString(it->second));
359 } // If not found it means we do not have any ids
360 }
361
362 if (!attributes.empty()) {
363 schema += " (";
364 for (const auto &attribute : attributes) {
365 schema += attribute + ",";
366 }
367 schema.pop_back();
368 schema += ")";
369 }
370
371 schema += ";\n";
372 }
373 }
374 schema += "}\n\n";
375 }
376 return schema;
377 }
378
GenerateFBS(const Parser & parser,const std::string & path,const std::string & file_name,bool no_log=false)379 static bool GenerateFBS(const Parser &parser, const std::string &path,
380 const std::string &file_name, bool no_log = false) {
381 const std::string fbs = GenerateFBS(parser, file_name, no_log);
382 if (fbs.empty()) { return false; }
383 // TODO: Use LogCompilerWarn
384 if (!no_log) {
385 fprintf(stderr,
386 "When you use --proto, that you should check for conformity "
387 "yourself, using the existing --conform");
388 }
389 return SaveFile((path + file_name + ".fbs").c_str(), fbs, false);
390 }
391
392 class FBSCodeGenerator : public CodeGenerator {
393 public:
FBSCodeGenerator(const bool no_log)394 explicit FBSCodeGenerator(const bool no_log) : no_log_(no_log) {}
395
GenerateCode(const Parser & parser,const std::string & path,const std::string & filename)396 Status GenerateCode(const Parser &parser, const std::string &path,
397 const std::string &filename) override {
398 if (!GenerateFBS(parser, path, filename, no_log_)) { return Status::ERROR; }
399 return Status::OK;
400 }
401
GenerateCodeString(const Parser & parser,const std::string & filename,std::string & output)402 Status GenerateCodeString(const Parser &parser, const std::string &filename,
403 std::string &output) override {
404 output = GenerateFBS(parser, filename, no_log_);
405 return Status::OK;
406 }
407
408 // Generate code from the provided `buffer` of given `length`. The buffer is a
409 // serialized reflection.fbs.
GenerateCode(const uint8_t *,int64_t,const CodeGenOptions &)410 Status GenerateCode(const uint8_t *, int64_t,
411 const CodeGenOptions &) override {
412 return Status::NOT_IMPLEMENTED;
413 }
414
GenerateMakeRule(const Parser & parser,const std::string & path,const std::string & filename,std::string & output)415 Status GenerateMakeRule(const Parser &parser, const std::string &path,
416 const std::string &filename,
417 std::string &output) override {
418 (void)parser;
419 (void)path;
420 (void)filename;
421 (void)output;
422 return Status::NOT_IMPLEMENTED;
423 }
424
GenerateGrpcCode(const Parser & parser,const std::string & path,const std::string & filename)425 Status GenerateGrpcCode(const Parser &parser, const std::string &path,
426 const std::string &filename) override {
427 (void)parser;
428 (void)path;
429 (void)filename;
430 return Status::NOT_IMPLEMENTED;
431 }
432
GenerateRootFile(const Parser & parser,const std::string & path)433 Status GenerateRootFile(const Parser &parser,
434 const std::string &path) override {
435 (void)parser;
436 (void)path;
437 return Status::NOT_IMPLEMENTED;
438 }
439
IsSchemaOnly() const440 bool IsSchemaOnly() const override { return false; }
441
SupportsBfbsGeneration() const442 bool SupportsBfbsGeneration() const override { return false; }
443
SupportsRootFileGeneration() const444 bool SupportsRootFileGeneration() const override { return false; }
445
Language() const446 IDLOptions::Language Language() const override { return IDLOptions::kProto; }
447
LanguageName() const448 std::string LanguageName() const override { return "proto"; }
449
450 protected:
451 const bool no_log_;
452 };
453
454 } // namespace
455
NewFBSCodeGenerator(const bool no_log)456 std::unique_ptr<CodeGenerator> NewFBSCodeGenerator(const bool no_log) {
457 return std::unique_ptr<FBSCodeGenerator>(new FBSCodeGenerator(no_log));
458 }
459
460 } // namespace flatbuffers
461