| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "src/protozero/protoc_plugin/protozero_generator.h" |
| |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| |
| #include "google/protobuf/descriptor.h" |
| #include "google/protobuf/io/printer.h" |
| #include "google/protobuf/io/zero_copy_stream.h" |
| #include "google/protobuf/stubs/strutil.h" |
| |
| namespace protozero { |
| |
| using google::protobuf::Descriptor; // Message descriptor. |
| using google::protobuf::EnumDescriptor; |
| using google::protobuf::EnumValueDescriptor; |
| using google::protobuf::FieldDescriptor; |
| using google::protobuf::FileDescriptor; |
| using google::protobuf::compiler::GeneratorContext; |
| using google::protobuf::io::Printer; |
| using google::protobuf::io::ZeroCopyOutputStream; |
| |
| using google::protobuf::Split; |
| using google::protobuf::StripPrefixString; |
| using google::protobuf::StripString; |
| using google::protobuf::StripSuffixString; |
| using google::protobuf::UpperString; |
| |
| namespace { |
| |
| inline std::string ProtoStubName(const FileDescriptor* proto) { |
| return StripSuffixString(proto->name(), ".proto") + ".pbzero"; |
| } |
| |
| class GeneratorJob { |
| public: |
| GeneratorJob(const FileDescriptor* file, |
| Printer* stub_h_printer, |
| Printer* stub_cc_printer) |
| : source_(file), stub_h_(stub_h_printer), stub_cc_(stub_cc_printer) {} |
| |
| bool GenerateStubs() { |
| Preprocess(); |
| GeneratePrologue(); |
| for (const EnumDescriptor* enumeration : enums_) |
| GenerateEnumDescriptor(enumeration); |
| for (const Descriptor* message : messages_) |
| GenerateMessageDescriptor(message); |
| GenerateEpilogue(); |
| return error_.empty(); |
| } |
| |
| void SetOption(const std::string& name, const std::string& value) { |
| if (name == "wrapper_namespace") { |
| wrapper_namespace_ = value; |
| } else { |
| Abort(std::string() + "Unknown plugin option '" + name + "'."); |
| } |
| } |
| |
| // If generator fails to produce stubs for a particular proto definitions |
| // it finishes with undefined output and writes the first error occured. |
| const std::string& GetFirstError() const { return error_; } |
| |
| private: |
| // Only the first error will be recorded. |
| void Abort(const std::string& reason) { |
| if (error_.empty()) |
| error_ = reason; |
| } |
| |
| // Get full name (including outer descriptors) of proto descriptor. |
| template <class T> |
| inline std::string GetDescriptorName(const T* descriptor) { |
| if (!package_.empty()) { |
| return StripPrefixString(descriptor->full_name(), package_ + "."); |
| } else { |
| return descriptor->full_name(); |
| } |
| } |
| |
| // Get C++ class name corresponding to proto descriptor. |
| // Nested names are splitted by underscores. Underscores in type names aren't |
| // prohibited but not recommended in order to avoid name collisions. |
| template <class T> |
| inline std::string GetCppClassName(const T* descriptor, bool full = false) { |
| std::string name = GetDescriptorName(descriptor); |
| StripString(&name, ".", '_'); |
| if (full) |
| name = full_namespace_prefix_ + name; |
| return name; |
| } |
| |
| inline std::string GetFieldNumberConstant(const FieldDescriptor* field) { |
| std::string name = field->camelcase_name(); |
| if (!name.empty()) { |
| name.at(0) = static_cast<char>(toupper(name.at(0))); |
| name = "k" + name + "FieldNumber"; |
| } else { |
| // Protoc allows fields like 'bool _ = 1'. |
| Abort("Empty field name in camel case notation."); |
| } |
| return name; |
| } |
| |
| // Small enums can be written faster without involving VarInt encoder. |
| inline bool IsTinyEnumField(const FieldDescriptor* field) { |
| if (field->type() != FieldDescriptor::TYPE_ENUM) |
| return false; |
| const EnumDescriptor* enumeration = field->enum_type(); |
| |
| for (int i = 0; i < enumeration->value_count(); ++i) { |
| int32_t value = enumeration->value(i)->number(); |
| if (value < 0 || value > 0x7F) |
| return false; |
| } |
| return true; |
| } |
| |
| void CollectDescriptors() { |
| // Collect message descriptors in DFS order. |
| std::vector<const Descriptor*> stack; |
| for (int i = 0; i < source_->message_type_count(); ++i) |
| stack.push_back(source_->message_type(i)); |
| |
| while (!stack.empty()) { |
| const Descriptor* message = stack.back(); |
| stack.pop_back(); |
| messages_.push_back(message); |
| for (int i = 0; i < message->nested_type_count(); ++i) { |
| stack.push_back(message->nested_type(i)); |
| } |
| } |
| |
| // Collect enums. |
| for (int i = 0; i < source_->enum_type_count(); ++i) |
| enums_.push_back(source_->enum_type(i)); |
| |
| for (const Descriptor* message : messages_) { |
| for (int i = 0; i < message->enum_type_count(); ++i) { |
| enums_.push_back(message->enum_type(i)); |
| } |
| } |
| } |
| |
| void CollectDependencies() { |
| // Public import basically means that callers only need to import this |
| // proto in order to use the stuff publicly imported by this proto. |
| for (int i = 0; i < source_->public_dependency_count(); ++i) |
| public_imports_.insert(source_->public_dependency(i)); |
| |
| if (source_->weak_dependency_count() > 0) |
| Abort("Weak imports are not supported."); |
| |
| // Sanity check. Collect public imports (of collected imports) in DFS order. |
| // Visibilty for current proto: |
| // - all imports listed in current proto, |
| // - public imports of everything imported (recursive). |
| std::vector<const FileDescriptor*> stack; |
| for (int i = 0; i < source_->dependency_count(); ++i) { |
| const FileDescriptor* import = source_->dependency(i); |
| stack.push_back(import); |
| if (public_imports_.count(import) == 0) { |
| private_imports_.insert(import); |
| } |
| } |
| |
| while (!stack.empty()) { |
| const FileDescriptor* import = stack.back(); |
| stack.pop_back(); |
| // Having imports under different packages leads to unnecessary |
| // complexity with namespaces. |
| if (import->package() != package_) |
| Abort("Imported proto must be in the same package."); |
| |
| for (int i = 0; i < import->public_dependency_count(); ++i) { |
| stack.push_back(import->public_dependency(i)); |
| } |
| } |
| |
| // Collect descriptors of messages and enums used in current proto. |
| // It will be used to generate necessary forward declarations and performed |
| // sanity check guarantees that everything lays in the same namespace. |
| for (const Descriptor* message : messages_) { |
| for (int i = 0; i < message->field_count(); ++i) { |
| const FieldDescriptor* field = message->field(i); |
| |
| if (field->type() == FieldDescriptor::TYPE_MESSAGE) { |
| if (public_imports_.count(field->message_type()->file()) == 0) { |
| // Avoid multiple forward declarations since |
| // public imports have been already included. |
| referenced_messages_.insert(field->message_type()); |
| } |
| } else if (field->type() == FieldDescriptor::TYPE_ENUM) { |
| if (public_imports_.count(field->enum_type()->file()) == 0) { |
| referenced_enums_.insert(field->enum_type()); |
| } |
| } |
| } |
| } |
| } |
| |
| void Preprocess() { |
| // Package name maps to a series of namespaces. |
| package_ = source_->package(); |
| namespaces_ = Split(package_, "."); |
| if (!wrapper_namespace_.empty()) |
| namespaces_.push_back(wrapper_namespace_); |
| |
| full_namespace_prefix_ = "::"; |
| for (const std::string& ns : namespaces_) |
| full_namespace_prefix_ += ns + "::"; |
| |
| CollectDescriptors(); |
| CollectDependencies(); |
| } |
| |
| // Print top header, namespaces and forward declarations. |
| void GeneratePrologue() { |
| std::string greeting = |
| "// Autogenerated by the ProtoZero compiler plugin. DO NOT EDIT.\n"; |
| std::string guard = package_ + "_" + source_->name() + "_H_"; |
| UpperString(&guard); |
| StripString(&guard, ".-/\\", '_'); |
| |
| stub_h_->Print( |
| "$greeting$\n" |
| "#ifndef $guard$\n" |
| "#define $guard$\n\n" |
| "#include <stddef.h>\n" |
| "#include <stdint.h>\n\n" |
| "#include \"perfetto/protozero/proto_field_descriptor.h\"\n" |
| "#include \"perfetto/protozero/protozero_message.h\"\n", |
| "greeting", greeting, "guard", guard); |
| stub_cc_->Print( |
| "$greeting$\n" |
| "#include \"$name$.h\"\n", |
| "greeting", greeting, "name", ProtoStubName(source_)); |
| |
| // Print includes for public imports. |
| for (const FileDescriptor* dependency : public_imports_) { |
| // Dependency name could contain slashes but importing from upper-level |
| // directories is not possible anyway since build system processes each |
| // proto file individually. Hence proto lookup path is always equal to the |
| // directory where particular proto file is located and protoc does not |
| // allow reference to upper directory (aka ..) in import path. |
| // |
| // Laconically said: |
| // - source_->name() may never have slashes, |
| // - dependency->name() may have slashes but always refers to inner path. |
| stub_h_->Print("#include \"$name$.h\"\n", "name", |
| ProtoStubName(dependency)); |
| } |
| stub_h_->Print("\n"); |
| |
| // Print includes for private imports to .cc file. |
| for (const FileDescriptor* dependency : private_imports_) { |
| stub_cc_->Print("#include \"$name$.h\"\n", "name", |
| ProtoStubName(dependency)); |
| } |
| stub_cc_->Print("\n"); |
| |
| if (messages_.size() > 0) { |
| stub_cc_->Print( |
| "namespace {\n" |
| " static const ::protozero::ProtoFieldDescriptor " |
| "kInvalidField = {\"\", " |
| "::protozero::ProtoFieldDescriptor::Type::TYPE_INVALID, " |
| "0, false};\n" |
| "}\n\n"); |
| } |
| |
| // Print namespaces. |
| for (const std::string& ns : namespaces_) { |
| stub_h_->Print("namespace $ns$ {\n", "ns", ns); |
| stub_cc_->Print("namespace $ns$ {\n", "ns", ns); |
| } |
| stub_h_->Print("\n"); |
| stub_cc_->Print("\n"); |
| |
| // Print forward declarations. |
| for (const Descriptor* message : referenced_messages_) { |
| stub_h_->Print("class $class$;\n", "class", GetCppClassName(message)); |
| } |
| for (const EnumDescriptor* enumeration : referenced_enums_) { |
| stub_h_->Print("enum $class$ : int32_t;\n", "class", |
| GetCppClassName(enumeration)); |
| } |
| stub_h_->Print("\n"); |
| } |
| |
| void GenerateEnumDescriptor(const EnumDescriptor* enumeration) { |
| stub_h_->Print("enum $class$ : int32_t {\n", "class", |
| GetCppClassName(enumeration)); |
| stub_h_->Indent(); |
| |
| std::string value_name_prefix; |
| if (enumeration->containing_type() != nullptr) |
| value_name_prefix = GetCppClassName(enumeration) + "_"; |
| |
| for (int i = 0; i < enumeration->value_count(); ++i) { |
| const EnumValueDescriptor* value = enumeration->value(i); |
| stub_h_->Print("$name$ = $number$,\n", "name", |
| value_name_prefix + value->name(), "number", |
| std::to_string(value->number())); |
| } |
| |
| stub_h_->Outdent(); |
| stub_h_->Print("};\n\n"); |
| } |
| |
| void GenerateSimpleFieldDescriptor(const FieldDescriptor* field) { |
| std::map<std::string, std::string> setter; |
| setter["id"] = std::to_string(field->number()); |
| setter["name"] = field->name(); |
| setter["action"] = field->is_repeated() ? "add" : "set"; |
| |
| std::string appender; |
| std::string cpp_type; |
| |
| switch (field->type()) { |
| case FieldDescriptor::TYPE_BOOL: { |
| appender = "AppendTinyVarInt"; |
| cpp_type = "bool"; |
| break; |
| } |
| case FieldDescriptor::TYPE_INT32: { |
| appender = "AppendVarInt"; |
| cpp_type = "int32_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_INT64: { |
| appender = "AppendVarInt"; |
| cpp_type = "int64_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_UINT32: { |
| appender = "AppendVarInt"; |
| cpp_type = "uint32_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_UINT64: { |
| appender = "AppendVarInt"; |
| cpp_type = "uint64_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_SINT32: { |
| appender = "AppendSignedVarInt"; |
| cpp_type = "int32_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_SINT64: { |
| appender = "AppendSignedVarInt"; |
| cpp_type = "int64_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_FIXED32: { |
| appender = "AppendFixed"; |
| cpp_type = "uint32_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_FIXED64: { |
| appender = "AppendFixed"; |
| cpp_type = "uint64_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_SFIXED32: { |
| appender = "AppendFixed"; |
| cpp_type = "int32_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_SFIXED64: { |
| appender = "AppendFixed"; |
| cpp_type = "int64_t"; |
| break; |
| } |
| case FieldDescriptor::TYPE_FLOAT: { |
| appender = "AppendFixed"; |
| cpp_type = "float"; |
| break; |
| } |
| case FieldDescriptor::TYPE_DOUBLE: { |
| appender = "AppendFixed"; |
| cpp_type = "double"; |
| break; |
| } |
| case FieldDescriptor::TYPE_ENUM: { |
| appender = IsTinyEnumField(field) ? "AppendTinyVarInt" : "AppendVarInt"; |
| cpp_type = GetCppClassName(field->enum_type(), true); |
| break; |
| } |
| case FieldDescriptor::TYPE_STRING: { |
| appender = "AppendString"; |
| cpp_type = "const char*"; |
| break; |
| } |
| case FieldDescriptor::TYPE_BYTES: { |
| stub_h_->Print( |
| setter, |
| "void $action$_$name$(const uint8_t* data, size_t size) {\n" |
| " AppendBytes($id$, data, size);\n" |
| "}\n"); |
| return; |
| } |
| case FieldDescriptor::TYPE_GROUP: |
| case FieldDescriptor::TYPE_MESSAGE: { |
| Abort("Unsupported field type."); |
| return; |
| } |
| } |
| setter["appender"] = appender; |
| setter["cpp_type"] = cpp_type; |
| stub_h_->Print(setter, |
| "void $action$_$name$($cpp_type$ value) {\n" |
| " $appender$($id$, value);\n" |
| "}\n"); |
| |
| // For strings also generate a variant for non-null terminated strings. |
| if (field->type() == FieldDescriptor::TYPE_STRING) { |
| stub_h_->Print(setter, |
| "// Doesn't check for null terminator.\n" |
| "// Expects |value| to be at least |size| long.\n" |
| "void $action$_$name$($cpp_type$ value, size_t size) {\n" |
| " AppendBytes($id$, value, size);\n" |
| "}\n"); |
| } |
| } |
| |
| void GenerateNestedMessageFieldDescriptor(const FieldDescriptor* field) { |
| std::string action = field->is_repeated() ? "add" : "set"; |
| std::string inner_class = GetCppClassName(field->message_type()); |
| std::string outer_class = GetCppClassName(field->containing_type()); |
| |
| stub_h_->Print("$inner_class$* $action$_$name$();\n", "name", field->name(), |
| "action", action, "inner_class", inner_class); |
| stub_cc_->Print( |
| "$inner_class$* $outer_class$::$action$_$name$() {\n" |
| " return BeginNestedMessage<$inner_class$>($id$);\n" |
| "}\n\n", |
| "id", std::to_string(field->number()), "name", field->name(), "action", |
| action, "inner_class", inner_class, "outer_class", outer_class); |
| } |
| |
| void GenerateReflectionForMessageFields(const Descriptor* message) { |
| const bool has_fields = (message->field_count() > 0); |
| |
| // Field number constants. |
| if (has_fields) { |
| stub_h_->Print("enum : int32_t {\n"); |
| stub_h_->Indent(); |
| |
| for (int i = 0; i < message->field_count(); ++i) { |
| const FieldDescriptor* field = message->field(i); |
| stub_h_->Print("$name$ = $id$,\n", "name", |
| GetFieldNumberConstant(field), "id", |
| std::to_string(field->number())); |
| } |
| stub_h_->Outdent(); |
| stub_h_->Print("};\n"); |
| } |
| |
| // Fields reflection table. |
| stub_h_->Print( |
| "static const ::protozero::ProtoFieldDescriptor* " |
| "GetFieldDescriptor(uint32_t field_id);\n"); |
| |
| std::string class_name = GetCppClassName(message); |
| if (has_fields) { |
| stub_cc_->Print( |
| "static const ::protozero::ProtoFieldDescriptor " |
| "kFields_$class$[] = {\n", |
| "class", class_name); |
| stub_cc_->Indent(); |
| for (int i = 0; i < message->field_count(); ++i) { |
| const FieldDescriptor* field = message->field(i); |
| std::string type_const = |
| std::string("TYPE_") + FieldDescriptor::TypeName(field->type()); |
| UpperString(&type_const); |
| stub_cc_->Print( |
| "{\"$name$\", " |
| "::protozero::ProtoFieldDescriptor::Type::$type$, " |
| "$number$, $is_repeated$},\n", |
| "name", field->name(), "type", type_const, "number", |
| std::to_string(field->number()), "is_repeated", |
| std::to_string(field->is_repeated())); |
| } |
| stub_cc_->Outdent(); |
| stub_cc_->Print("};\n\n"); |
| } |
| |
| // Fields reflection getter. |
| stub_cc_->Print( |
| "const ::protozero::ProtoFieldDescriptor* " |
| "$class$::GetFieldDescriptor(uint32_t field_id) {\n", |
| "class", class_name); |
| stub_cc_->Indent(); |
| if (has_fields) { |
| stub_cc_->Print("switch (field_id) {\n"); |
| stub_cc_->Indent(); |
| for (int i = 0; i < message->field_count(); ++i) { |
| stub_cc_->Print( |
| "case $field$:\n" |
| " return &kFields_$class$[$id$];\n", |
| "class", class_name, "field", |
| GetFieldNumberConstant(message->field(i)), "id", std::to_string(i)); |
| } |
| stub_cc_->Print( |
| "default:\n" |
| " return &kInvalidField;\n"); |
| stub_cc_->Outdent(); |
| stub_cc_->Print("}\n"); |
| } else { |
| stub_cc_->Print("return &kInvalidField;\n"); |
| } |
| stub_cc_->Outdent(); |
| stub_cc_->Print("}\n\n"); |
| } |
| |
| void GenerateMessageDescriptor(const Descriptor* message) { |
| stub_h_->Print( |
| "class $name$ : public ::protozero::ProtoZeroMessage {\n" |
| " public:\n", |
| "name", GetCppClassName(message)); |
| stub_h_->Indent(); |
| |
| GenerateReflectionForMessageFields(message); |
| |
| // Using statements for nested messages. |
| for (int i = 0; i < message->nested_type_count(); ++i) { |
| const Descriptor* nested_message = message->nested_type(i); |
| stub_h_->Print("using $local_name$ = $global_name$;\n", "local_name", |
| nested_message->name(), "global_name", |
| GetCppClassName(nested_message, true)); |
| } |
| |
| // Using statements for nested enums. |
| for (int i = 0; i < message->enum_type_count(); ++i) { |
| const EnumDescriptor* nested_enum = message->enum_type(i); |
| stub_h_->Print("using $local_name$ = $global_name$;\n", "local_name", |
| nested_enum->name(), "global_name", |
| GetCppClassName(nested_enum, true)); |
| } |
| |
| // Values of nested enums. |
| for (int i = 0; i < message->enum_type_count(); ++i) { |
| const EnumDescriptor* nested_enum = message->enum_type(i); |
| std::string value_name_prefix = GetCppClassName(nested_enum) + "_"; |
| |
| for (int j = 0; j < nested_enum->value_count(); ++j) { |
| const EnumValueDescriptor* value = nested_enum->value(j); |
| stub_h_->Print("static const $class$ $name$ = $full_name$;\n", "class", |
| nested_enum->name(), "name", value->name(), "full_name", |
| value_name_prefix + value->name()); |
| } |
| } |
| |
| // Field descriptors. |
| for (int i = 0; i < message->field_count(); ++i) { |
| const FieldDescriptor* field = message->field(i); |
| if (field->is_packed()) { |
| Abort("Packed repeated fields are not supported."); |
| return; |
| } |
| if (field->type() != FieldDescriptor::TYPE_MESSAGE) { |
| GenerateSimpleFieldDescriptor(field); |
| } else { |
| GenerateNestedMessageFieldDescriptor(field); |
| } |
| } |
| |
| stub_h_->Outdent(); |
| stub_h_->Print("};\n\n"); |
| } |
| |
| void GenerateEpilogue() { |
| for (unsigned i = 0; i < namespaces_.size(); ++i) { |
| stub_h_->Print("} // Namespace.\n"); |
| stub_cc_->Print("} // Namespace.\n"); |
| } |
| stub_h_->Print("#endif // Include guard.\n"); |
| } |
| |
| const FileDescriptor* const source_; |
| Printer* const stub_h_; |
| Printer* const stub_cc_; |
| std::string error_; |
| |
| std::string package_; |
| std::string wrapper_namespace_; |
| std::vector<std::string> namespaces_; |
| std::string full_namespace_prefix_; |
| std::vector<const Descriptor*> messages_; |
| std::vector<const EnumDescriptor*> enums_; |
| |
| std::set<const FileDescriptor*> public_imports_; |
| std::set<const FileDescriptor*> private_imports_; |
| std::set<const Descriptor*> referenced_messages_; |
| std::set<const EnumDescriptor*> referenced_enums_; |
| }; |
| |
| } // namespace |
| |
| ProtoZeroGenerator::ProtoZeroGenerator() {} |
| |
| ProtoZeroGenerator::~ProtoZeroGenerator() {} |
| |
| bool ProtoZeroGenerator::Generate(const FileDescriptor* file, |
| const std::string& options, |
| GeneratorContext* context, |
| std::string* error) const { |
| const std::unique_ptr<ZeroCopyOutputStream> stub_h_file_stream( |
| context->Open(ProtoStubName(file) + ".h")); |
| const std::unique_ptr<ZeroCopyOutputStream> stub_cc_file_stream( |
| context->Open(ProtoStubName(file) + ".cc")); |
| |
| // Variables are delimited by $. |
| Printer stub_h_printer(stub_h_file_stream.get(), '$'); |
| Printer stub_cc_printer(stub_cc_file_stream.get(), '$'); |
| GeneratorJob job(file, &stub_h_printer, &stub_cc_printer); |
| |
| // Parse additional options. |
| for (const std::string& option : Split(options, ",")) { |
| std::vector<std::string> option_pair = Split(option, "="); |
| job.SetOption(option_pair[0], option_pair[1]); |
| } |
| |
| if (!job.GenerateStubs()) { |
| *error = job.GetFirstError(); |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace protozero |