1 /*
2 * Copyright (C) 2017 The Android Open Source Project
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 #include "src/protozero/protoc_plugin/protozero_generator.h"
18
19 #include <map>
20 #include <memory>
21 #include <set>
22 #include <string>
23
24 #include "google/protobuf/descriptor.h"
25 #include "google/protobuf/io/printer.h"
26 #include "google/protobuf/io/zero_copy_stream.h"
27 #include "google/protobuf/stubs/strutil.h"
28
29 namespace protozero {
30
31 using google::protobuf::Descriptor; // Message descriptor.
32 using google::protobuf::EnumDescriptor;
33 using google::protobuf::EnumValueDescriptor;
34 using google::protobuf::FieldDescriptor;
35 using google::protobuf::FileDescriptor;
36 using google::protobuf::compiler::GeneratorContext;
37 using google::protobuf::io::Printer;
38 using google::protobuf::io::ZeroCopyOutputStream;
39
40 using google::protobuf::Split;
41 using google::protobuf::StripPrefixString;
42 using google::protobuf::StripString;
43 using google::protobuf::StripSuffixString;
44 using google::protobuf::UpperString;
45
46 namespace {
47
ProtoStubName(const FileDescriptor * proto)48 inline std::string ProtoStubName(const FileDescriptor* proto) {
49 return StripSuffixString(proto->name(), ".proto") + ".pbzero";
50 }
51
52 class GeneratorJob {
53 public:
GeneratorJob(const FileDescriptor * file,Printer * stub_h_printer,Printer * stub_cc_printer)54 GeneratorJob(const FileDescriptor* file,
55 Printer* stub_h_printer,
56 Printer* stub_cc_printer)
57 : source_(file), stub_h_(stub_h_printer), stub_cc_(stub_cc_printer) {}
58
GenerateStubs()59 bool GenerateStubs() {
60 Preprocess();
61 GeneratePrologue();
62 for (const EnumDescriptor* enumeration : enums_)
63 GenerateEnumDescriptor(enumeration);
64 for (const Descriptor* message : messages_)
65 GenerateMessageDescriptor(message);
66 GenerateEpilogue();
67 return error_.empty();
68 }
69
SetOption(const std::string & name,const std::string & value)70 void SetOption(const std::string& name, const std::string& value) {
71 if (name == "wrapper_namespace") {
72 wrapper_namespace_ = value;
73 } else {
74 Abort(std::string() + "Unknown plugin option '" + name + "'.");
75 }
76 }
77
78 // If generator fails to produce stubs for a particular proto definitions
79 // it finishes with undefined output and writes the first error occured.
GetFirstError() const80 const std::string& GetFirstError() const { return error_; }
81
82 private:
83 // Only the first error will be recorded.
Abort(const std::string & reason)84 void Abort(const std::string& reason) {
85 if (error_.empty())
86 error_ = reason;
87 }
88
89 // Get full name (including outer descriptors) of proto descriptor.
90 template <class T>
GetDescriptorName(const T * descriptor)91 inline std::string GetDescriptorName(const T* descriptor) {
92 if (!package_.empty()) {
93 return StripPrefixString(descriptor->full_name(), package_ + ".");
94 } else {
95 return descriptor->full_name();
96 }
97 }
98
99 // Get C++ class name corresponding to proto descriptor.
100 // Nested names are splitted by underscores. Underscores in type names aren't
101 // prohibited but not recommended in order to avoid name collisions.
102 template <class T>
GetCppClassName(const T * descriptor,bool full=false)103 inline std::string GetCppClassName(const T* descriptor, bool full = false) {
104 std::string name = GetDescriptorName(descriptor);
105 StripString(&name, ".", '_');
106 if (full)
107 name = full_namespace_prefix_ + name;
108 return name;
109 }
110
GetFieldNumberConstant(const FieldDescriptor * field)111 inline std::string GetFieldNumberConstant(const FieldDescriptor* field) {
112 std::string name = field->camelcase_name();
113 if (!name.empty()) {
114 name.at(0) = static_cast<char>(toupper(name.at(0)));
115 name = "k" + name + "FieldNumber";
116 } else {
117 // Protoc allows fields like 'bool _ = 1'.
118 Abort("Empty field name in camel case notation.");
119 }
120 return name;
121 }
122
123 // Small enums can be written faster without involving VarInt encoder.
IsTinyEnumField(const FieldDescriptor * field)124 inline bool IsTinyEnumField(const FieldDescriptor* field) {
125 if (field->type() != FieldDescriptor::TYPE_ENUM)
126 return false;
127 const EnumDescriptor* enumeration = field->enum_type();
128
129 for (int i = 0; i < enumeration->value_count(); ++i) {
130 int32_t value = enumeration->value(i)->number();
131 if (value < 0 || value > 0x7F)
132 return false;
133 }
134 return true;
135 }
136
CollectDescriptors()137 void CollectDescriptors() {
138 // Collect message descriptors in DFS order.
139 std::vector<const Descriptor*> stack;
140 for (int i = 0; i < source_->message_type_count(); ++i)
141 stack.push_back(source_->message_type(i));
142
143 while (!stack.empty()) {
144 const Descriptor* message = stack.back();
145 stack.pop_back();
146 messages_.push_back(message);
147 for (int i = 0; i < message->nested_type_count(); ++i) {
148 stack.push_back(message->nested_type(i));
149 }
150 }
151
152 // Collect enums.
153 for (int i = 0; i < source_->enum_type_count(); ++i)
154 enums_.push_back(source_->enum_type(i));
155
156 for (const Descriptor* message : messages_) {
157 for (int i = 0; i < message->enum_type_count(); ++i) {
158 enums_.push_back(message->enum_type(i));
159 }
160 }
161 }
162
CollectDependencies()163 void CollectDependencies() {
164 // Public import basically means that callers only need to import this
165 // proto in order to use the stuff publicly imported by this proto.
166 for (int i = 0; i < source_->public_dependency_count(); ++i)
167 public_imports_.insert(source_->public_dependency(i));
168
169 if (source_->weak_dependency_count() > 0)
170 Abort("Weak imports are not supported.");
171
172 // Sanity check. Collect public imports (of collected imports) in DFS order.
173 // Visibilty for current proto:
174 // - all imports listed in current proto,
175 // - public imports of everything imported (recursive).
176 std::vector<const FileDescriptor*> stack;
177 for (int i = 0; i < source_->dependency_count(); ++i) {
178 const FileDescriptor* import = source_->dependency(i);
179 stack.push_back(import);
180 if (public_imports_.count(import) == 0) {
181 private_imports_.insert(import);
182 }
183 }
184
185 while (!stack.empty()) {
186 const FileDescriptor* import = stack.back();
187 stack.pop_back();
188 // Having imports under different packages leads to unnecessary
189 // complexity with namespaces.
190 if (import->package() != package_)
191 Abort("Imported proto must be in the same package.");
192
193 for (int i = 0; i < import->public_dependency_count(); ++i) {
194 stack.push_back(import->public_dependency(i));
195 }
196 }
197
198 // Collect descriptors of messages and enums used in current proto.
199 // It will be used to generate necessary forward declarations and performed
200 // sanity check guarantees that everything lays in the same namespace.
201 for (const Descriptor* message : messages_) {
202 for (int i = 0; i < message->field_count(); ++i) {
203 const FieldDescriptor* field = message->field(i);
204
205 if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
206 if (public_imports_.count(field->message_type()->file()) == 0) {
207 // Avoid multiple forward declarations since
208 // public imports have been already included.
209 referenced_messages_.insert(field->message_type());
210 }
211 } else if (field->type() == FieldDescriptor::TYPE_ENUM) {
212 if (public_imports_.count(field->enum_type()->file()) == 0) {
213 referenced_enums_.insert(field->enum_type());
214 }
215 }
216 }
217 }
218 }
219
Preprocess()220 void Preprocess() {
221 // Package name maps to a series of namespaces.
222 package_ = source_->package();
223 namespaces_ = Split(package_, ".");
224 if (!wrapper_namespace_.empty())
225 namespaces_.push_back(wrapper_namespace_);
226
227 full_namespace_prefix_ = "::";
228 for (const std::string& ns : namespaces_)
229 full_namespace_prefix_ += ns + "::";
230
231 CollectDescriptors();
232 CollectDependencies();
233 }
234
235 // Print top header, namespaces and forward declarations.
GeneratePrologue()236 void GeneratePrologue() {
237 std::string greeting =
238 "// Autogenerated by the ProtoZero compiler plugin. DO NOT EDIT.\n";
239 std::string guard = package_ + "_" + source_->name() + "_H_";
240 UpperString(&guard);
241 StripString(&guard, ".-/\\", '_');
242
243 stub_h_->Print(
244 "$greeting$\n"
245 "#ifndef $guard$\n"
246 "#define $guard$\n\n"
247 "#include <stddef.h>\n"
248 "#include <stdint.h>\n\n"
249 "#include \"perfetto/protozero/proto_field_descriptor.h\"\n"
250 "#include \"perfetto/protozero/message.h\"\n",
251 "greeting", greeting, "guard", guard);
252 stub_cc_->Print(
253 "$greeting$\n"
254 "#include \"$name$.h\"\n",
255 "greeting", greeting, "name", ProtoStubName(source_));
256
257 // Print includes for public imports.
258 for (const FileDescriptor* dependency : public_imports_) {
259 // Dependency name could contain slashes but importing from upper-level
260 // directories is not possible anyway since build system processes each
261 // proto file individually. Hence proto lookup path is always equal to the
262 // directory where particular proto file is located and protoc does not
263 // allow reference to upper directory (aka ..) in import path.
264 //
265 // Laconically said:
266 // - source_->name() may never have slashes,
267 // - dependency->name() may have slashes but always refers to inner path.
268 stub_h_->Print("#include \"$name$.h\"\n", "name",
269 ProtoStubName(dependency));
270 }
271 stub_h_->Print("\n");
272
273 // Print includes for private imports to .cc file.
274 for (const FileDescriptor* dependency : private_imports_) {
275 stub_cc_->Print("#include \"$name$.h\"\n", "name",
276 ProtoStubName(dependency));
277 }
278 stub_cc_->Print("\n");
279
280 if (messages_.size() > 0) {
281 stub_cc_->Print(
282 "namespace {\n"
283 " static const ::protozero::ProtoFieldDescriptor "
284 "kInvalidField = {\"\", "
285 "::protozero::ProtoFieldDescriptor::Type::TYPE_INVALID, "
286 "0, false};\n"
287 "}\n\n");
288 }
289
290 // Print namespaces.
291 for (const std::string& ns : namespaces_) {
292 stub_h_->Print("namespace $ns$ {\n", "ns", ns);
293 stub_cc_->Print("namespace $ns$ {\n", "ns", ns);
294 }
295 stub_h_->Print("\n");
296 stub_cc_->Print("\n");
297
298 // Print forward declarations.
299 for (const Descriptor* message : referenced_messages_) {
300 stub_h_->Print("class $class$;\n", "class", GetCppClassName(message));
301 }
302 for (const EnumDescriptor* enumeration : referenced_enums_) {
303 stub_h_->Print("enum $class$ : int32_t;\n", "class",
304 GetCppClassName(enumeration));
305 }
306 stub_h_->Print("\n");
307 }
308
GenerateEnumDescriptor(const EnumDescriptor * enumeration)309 void GenerateEnumDescriptor(const EnumDescriptor* enumeration) {
310 stub_h_->Print("enum $class$ : int32_t {\n", "class",
311 GetCppClassName(enumeration));
312 stub_h_->Indent();
313
314 std::string value_name_prefix;
315 if (enumeration->containing_type() != nullptr)
316 value_name_prefix = GetCppClassName(enumeration) + "_";
317
318 for (int i = 0; i < enumeration->value_count(); ++i) {
319 const EnumValueDescriptor* value = enumeration->value(i);
320 stub_h_->Print("$name$ = $number$,\n", "name",
321 value_name_prefix + value->name(), "number",
322 std::to_string(value->number()));
323 }
324
325 stub_h_->Outdent();
326 stub_h_->Print("};\n\n");
327 }
328
GenerateSimpleFieldDescriptor(const FieldDescriptor * field)329 void GenerateSimpleFieldDescriptor(const FieldDescriptor* field) {
330 std::map<std::string, std::string> setter;
331 setter["id"] = std::to_string(field->number());
332 setter["name"] = field->name();
333 setter["action"] = field->is_repeated() ? "add" : "set";
334
335 std::string appender;
336 std::string cpp_type;
337
338 switch (field->type()) {
339 case FieldDescriptor::TYPE_BOOL: {
340 appender = "AppendTinyVarInt";
341 cpp_type = "bool";
342 break;
343 }
344 case FieldDescriptor::TYPE_INT32: {
345 appender = "AppendVarInt";
346 cpp_type = "int32_t";
347 break;
348 }
349 case FieldDescriptor::TYPE_INT64: {
350 appender = "AppendVarInt";
351 cpp_type = "int64_t";
352 break;
353 }
354 case FieldDescriptor::TYPE_UINT32: {
355 appender = "AppendVarInt";
356 cpp_type = "uint32_t";
357 break;
358 }
359 case FieldDescriptor::TYPE_UINT64: {
360 appender = "AppendVarInt";
361 cpp_type = "uint64_t";
362 break;
363 }
364 case FieldDescriptor::TYPE_SINT32: {
365 appender = "AppendSignedVarInt";
366 cpp_type = "int32_t";
367 break;
368 }
369 case FieldDescriptor::TYPE_SINT64: {
370 appender = "AppendSignedVarInt";
371 cpp_type = "int64_t";
372 break;
373 }
374 case FieldDescriptor::TYPE_FIXED32: {
375 appender = "AppendFixed";
376 cpp_type = "uint32_t";
377 break;
378 }
379 case FieldDescriptor::TYPE_FIXED64: {
380 appender = "AppendFixed";
381 cpp_type = "uint64_t";
382 break;
383 }
384 case FieldDescriptor::TYPE_SFIXED32: {
385 appender = "AppendFixed";
386 cpp_type = "int32_t";
387 break;
388 }
389 case FieldDescriptor::TYPE_SFIXED64: {
390 appender = "AppendFixed";
391 cpp_type = "int64_t";
392 break;
393 }
394 case FieldDescriptor::TYPE_FLOAT: {
395 appender = "AppendFixed";
396 cpp_type = "float";
397 break;
398 }
399 case FieldDescriptor::TYPE_DOUBLE: {
400 appender = "AppendFixed";
401 cpp_type = "double";
402 break;
403 }
404 case FieldDescriptor::TYPE_ENUM: {
405 appender = IsTinyEnumField(field) ? "AppendTinyVarInt" : "AppendVarInt";
406 cpp_type = GetCppClassName(field->enum_type(), true);
407 break;
408 }
409 case FieldDescriptor::TYPE_STRING: {
410 appender = "AppendString";
411 cpp_type = "const char*";
412 break;
413 }
414 case FieldDescriptor::TYPE_BYTES: {
415 stub_h_->Print(
416 setter,
417 "void $action$_$name$(const uint8_t* data, size_t size) {\n"
418 " AppendBytes($id$, data, size);\n"
419 "}\n");
420 return;
421 }
422 case FieldDescriptor::TYPE_GROUP:
423 case FieldDescriptor::TYPE_MESSAGE: {
424 Abort("Unsupported field type.");
425 return;
426 }
427 }
428 setter["appender"] = appender;
429 setter["cpp_type"] = cpp_type;
430 stub_h_->Print(setter,
431 "void $action$_$name$($cpp_type$ value) {\n"
432 " $appender$($id$, value);\n"
433 "}\n");
434
435 // For strings also generate a variant for non-null terminated strings.
436 if (field->type() == FieldDescriptor::TYPE_STRING) {
437 stub_h_->Print(setter,
438 "// Doesn't check for null terminator.\n"
439 "// Expects |value| to be at least |size| long.\n"
440 "void $action$_$name$($cpp_type$ value, size_t size) {\n"
441 " AppendBytes($id$, value, size);\n"
442 "}\n");
443 }
444 }
445
GenerateNestedMessageFieldDescriptor(const FieldDescriptor * field)446 void GenerateNestedMessageFieldDescriptor(const FieldDescriptor* field) {
447 std::string action = field->is_repeated() ? "add" : "set";
448 std::string inner_class = GetCppClassName(field->message_type());
449 std::string outer_class = GetCppClassName(field->containing_type());
450
451 stub_h_->Print("$inner_class$* $action$_$name$();\n", "name", field->name(),
452 "action", action, "inner_class", inner_class);
453 stub_cc_->Print(
454 "$inner_class$* $outer_class$::$action$_$name$() {\n"
455 " return BeginNestedMessage<$inner_class$>($id$);\n"
456 "}\n\n",
457 "id", std::to_string(field->number()), "name", field->name(), "action",
458 action, "inner_class", inner_class, "outer_class", outer_class);
459 }
460
GenerateReflectionForMessageFields(const Descriptor * message)461 void GenerateReflectionForMessageFields(const Descriptor* message) {
462 const bool has_fields = (message->field_count() > 0);
463
464 // Field number constants.
465 if (has_fields) {
466 stub_h_->Print("enum : int32_t {\n");
467 stub_h_->Indent();
468
469 for (int i = 0; i < message->field_count(); ++i) {
470 const FieldDescriptor* field = message->field(i);
471 stub_h_->Print("$name$ = $id$,\n", "name",
472 GetFieldNumberConstant(field), "id",
473 std::to_string(field->number()));
474 }
475 stub_h_->Outdent();
476 stub_h_->Print("};\n");
477 }
478
479 // Fields reflection table.
480 stub_h_->Print(
481 "static const ::protozero::ProtoFieldDescriptor* "
482 "GetFieldDescriptor(uint32_t field_id);\n");
483
484 std::string class_name = GetCppClassName(message);
485 if (has_fields) {
486 stub_cc_->Print(
487 "static const ::protozero::ProtoFieldDescriptor "
488 "kFields_$class$[] = {\n",
489 "class", class_name);
490 stub_cc_->Indent();
491 for (int i = 0; i < message->field_count(); ++i) {
492 const FieldDescriptor* field = message->field(i);
493 std::string type_const =
494 std::string("TYPE_") + FieldDescriptor::TypeName(field->type());
495 UpperString(&type_const);
496 stub_cc_->Print(
497 "{\"$name$\", "
498 "::protozero::ProtoFieldDescriptor::Type::$type$, "
499 "$number$, $is_repeated$},\n",
500 "name", field->name(), "type", type_const, "number",
501 std::to_string(field->number()), "is_repeated",
502 std::to_string(field->is_repeated()));
503 }
504 stub_cc_->Outdent();
505 stub_cc_->Print("};\n\n");
506 }
507
508 // Fields reflection getter.
509 stub_cc_->Print(
510 "const ::protozero::ProtoFieldDescriptor* "
511 "$class$::GetFieldDescriptor(uint32_t field_id) {\n",
512 "class", class_name);
513 stub_cc_->Indent();
514 if (has_fields) {
515 stub_cc_->Print("switch (field_id) {\n");
516 stub_cc_->Indent();
517 for (int i = 0; i < message->field_count(); ++i) {
518 stub_cc_->Print(
519 "case $field$:\n"
520 " return &kFields_$class$[$id$];\n",
521 "class", class_name, "field",
522 GetFieldNumberConstant(message->field(i)), "id", std::to_string(i));
523 }
524 stub_cc_->Print(
525 "default:\n"
526 " return &kInvalidField;\n");
527 stub_cc_->Outdent();
528 stub_cc_->Print("}\n");
529 } else {
530 stub_cc_->Print("return &kInvalidField;\n");
531 }
532 stub_cc_->Outdent();
533 stub_cc_->Print("}\n\n");
534 }
535
GenerateMessageDescriptor(const Descriptor * message)536 void GenerateMessageDescriptor(const Descriptor* message) {
537 stub_h_->Print(
538 "class $name$ : public ::protozero::Message {\n"
539 " public:\n",
540 "name", GetCppClassName(message));
541 stub_h_->Indent();
542
543 GenerateReflectionForMessageFields(message);
544
545 // Using statements for nested messages.
546 for (int i = 0; i < message->nested_type_count(); ++i) {
547 const Descriptor* nested_message = message->nested_type(i);
548 stub_h_->Print("using $local_name$ = $global_name$;\n", "local_name",
549 nested_message->name(), "global_name",
550 GetCppClassName(nested_message, true));
551 }
552
553 // Using statements for nested enums.
554 for (int i = 0; i < message->enum_type_count(); ++i) {
555 const EnumDescriptor* nested_enum = message->enum_type(i);
556 stub_h_->Print("using $local_name$ = $global_name$;\n", "local_name",
557 nested_enum->name(), "global_name",
558 GetCppClassName(nested_enum, true));
559 }
560
561 // Values of nested enums.
562 for (int i = 0; i < message->enum_type_count(); ++i) {
563 const EnumDescriptor* nested_enum = message->enum_type(i);
564 std::string value_name_prefix = GetCppClassName(nested_enum) + "_";
565
566 for (int j = 0; j < nested_enum->value_count(); ++j) {
567 const EnumValueDescriptor* value = nested_enum->value(j);
568 stub_h_->Print("static const $class$ $name$ = $full_name$;\n", "class",
569 nested_enum->name(), "name", value->name(), "full_name",
570 value_name_prefix + value->name());
571 }
572 }
573
574 // Field descriptors.
575 for (int i = 0; i < message->field_count(); ++i) {
576 const FieldDescriptor* field = message->field(i);
577 if (field->is_packed()) {
578 Abort("Packed repeated fields are not supported.");
579 return;
580 }
581 if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
582 GenerateSimpleFieldDescriptor(field);
583 } else {
584 GenerateNestedMessageFieldDescriptor(field);
585 }
586 }
587
588 stub_h_->Outdent();
589 stub_h_->Print("};\n\n");
590 }
591
GenerateEpilogue()592 void GenerateEpilogue() {
593 for (unsigned i = 0; i < namespaces_.size(); ++i) {
594 stub_h_->Print("} // Namespace.\n");
595 stub_cc_->Print("} // Namespace.\n");
596 }
597 stub_h_->Print("#endif // Include guard.\n");
598 }
599
600 const FileDescriptor* const source_;
601 Printer* const stub_h_;
602 Printer* const stub_cc_;
603 std::string error_;
604
605 std::string package_;
606 std::string wrapper_namespace_;
607 std::vector<std::string> namespaces_;
608 std::string full_namespace_prefix_;
609 std::vector<const Descriptor*> messages_;
610 std::vector<const EnumDescriptor*> enums_;
611
612 std::set<const FileDescriptor*> public_imports_;
613 std::set<const FileDescriptor*> private_imports_;
614 std::set<const Descriptor*> referenced_messages_;
615 std::set<const EnumDescriptor*> referenced_enums_;
616 };
617
618 } // namespace
619
ProtoZeroGenerator()620 ProtoZeroGenerator::ProtoZeroGenerator() {}
621
~ProtoZeroGenerator()622 ProtoZeroGenerator::~ProtoZeroGenerator() {}
623
Generate(const FileDescriptor * file,const std::string & options,GeneratorContext * context,std::string * error) const624 bool ProtoZeroGenerator::Generate(const FileDescriptor* file,
625 const std::string& options,
626 GeneratorContext* context,
627 std::string* error) const {
628 const std::unique_ptr<ZeroCopyOutputStream> stub_h_file_stream(
629 context->Open(ProtoStubName(file) + ".h"));
630 const std::unique_ptr<ZeroCopyOutputStream> stub_cc_file_stream(
631 context->Open(ProtoStubName(file) + ".cc"));
632
633 // Variables are delimited by $.
634 Printer stub_h_printer(stub_h_file_stream.get(), '$');
635 Printer stub_cc_printer(stub_cc_file_stream.get(), '$');
636 GeneratorJob job(file, &stub_h_printer, &stub_cc_printer);
637
638 // Parse additional options.
639 for (const std::string& option : Split(options, ",")) {
640 std::vector<std::string> option_pair = Split(option, "=");
641 job.SetOption(option_pair[0], option_pair[1]);
642 }
643
644 if (!job.GenerateStubs()) {
645 *error = job.GetFirstError();
646 return false;
647 }
648 return true;
649 }
650
651 } // namespace protozero
652