| /* | 
 |  * Copyright (C) 2018 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/tools/ftrace_proto_gen/proto_gen_utils.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <fstream> | 
 | #include <regex> | 
 |  | 
 | #include "perfetto/base/logging.h" | 
 | #include "perfetto/ext/base/file_utils.h" | 
 | #include "perfetto/ext/base/pipe.h" | 
 | #include "perfetto/ext/base/string_splitter.h" | 
 | #include "perfetto/ext/base/string_utils.h" | 
 | #include "perfetto/ext/base/subprocess.h" | 
 |  | 
 | namespace perfetto { | 
 |  | 
 | namespace { | 
 |  | 
 | std::string RunClangFmt(const std::string& input) { | 
 | #if PERFETTO_BUILDFLAG(PERFETTO_OS_MAC) | 
 |   const std::string platform = "mac"; | 
 | #else | 
 |   const std::string platform = "linux64"; | 
 | #endif | 
 |   base::Subprocess clang_fmt({"buildtools/" + platform + "/clang-format"}); | 
 |   clang_fmt.args.stdout_mode = base::Subprocess::OutputMode::kBuffer; | 
 |   clang_fmt.args.stderr_mode = base::Subprocess::OutputMode::kInherit; | 
 |   clang_fmt.args.input = input; | 
 |   PERFETTO_CHECK(clang_fmt.Call()); | 
 |   return std::move(clang_fmt.output()); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | using base::Contains; | 
 | using base::EndsWith; | 
 | using base::StartsWith; | 
 | using base::Uppercase; | 
 |  | 
 | VerifyStream::VerifyStream(std::string filename) | 
 |     : filename_(std::move(filename)) { | 
 |   PERFETTO_CHECK(base::ReadFile(filename_, &expected_)); | 
 | } | 
 |  | 
 | VerifyStream::~VerifyStream() { | 
 |   std::string tidied = str(); | 
 |   if (EndsWith(filename_, "cc") || EndsWith(filename_, "proto")) | 
 |     tidied = RunClangFmt(str()); | 
 |   if (expected_ != tidied) { | 
 |     PERFETTO_FATAL("%s is out of date. Please run tools/run_ftrace_proto_gen.", | 
 |                    filename_.c_str()); | 
 |   } | 
 | } | 
 |  | 
 | FtraceEventName::FtraceEventName(const std::string& full_name) { | 
 |   if (full_name.rfind("removed", 0) != std::string::npos) { | 
 |     valid_ = false; | 
 |     return; | 
 |   } | 
 |   name_ = full_name.substr(full_name.find('/') + 1, std::string::npos); | 
 |   group_ = full_name.substr(0, full_name.find('/')); | 
 |   valid_ = true; | 
 | } | 
 |  | 
 | bool FtraceEventName::valid() const { | 
 |   return valid_; | 
 | } | 
 |  | 
 | const std::string& FtraceEventName::name() const { | 
 |   PERFETTO_CHECK(valid_); | 
 |   return name_; | 
 | } | 
 |  | 
 | const std::string& FtraceEventName::group() const { | 
 |   PERFETTO_CHECK(valid_); | 
 |   return group_; | 
 | } | 
 |  | 
 | std::string ToCamelCase(const std::string& s) { | 
 |   std::string result; | 
 |   result.reserve(s.size()); | 
 |   bool upperCaseNextChar = true; | 
 |   for (size_t i = 0; i < s.size(); i++) { | 
 |     char c = s[i]; | 
 |     if (c == '_') { | 
 |       upperCaseNextChar = true; | 
 |       continue; | 
 |     } | 
 |     if (upperCaseNextChar) { | 
 |       upperCaseNextChar = false; | 
 |       c = Uppercase(c); | 
 |     } | 
 |     result.push_back(c); | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | ProtoType ProtoType::GetSigned() const { | 
 |   PERFETTO_CHECK(type == NUMERIC); | 
 |   if (is_signed) | 
 |     return *this; | 
 |  | 
 |   if (size == 64) { | 
 |     return Numeric(64, true); | 
 |   } | 
 |  | 
 |   return Numeric(2 * size, true); | 
 | } | 
 |  | 
 | std::string ProtoType::ToString() const { | 
 |   switch (type) { | 
 |     case INVALID: | 
 |       PERFETTO_CHECK(false); | 
 |     case STRING: | 
 |       return "string"; | 
 |     case NUMERIC: { | 
 |       std::string s; | 
 |       if (!is_signed) | 
 |         s += "u"; | 
 |       s += "int"; | 
 |       s += std::to_string(size); | 
 |       return s; | 
 |     } | 
 |   } | 
 |   PERFETTO_CHECK(false);  // for GCC. | 
 | } | 
 |  | 
 | // static | 
 | ProtoType ProtoType::String() { | 
 |   return {STRING, 0, false}; | 
 | } | 
 |  | 
 | // static | 
 | ProtoType ProtoType::Invalid() { | 
 |   return {INVALID, 0, false}; | 
 | } | 
 |  | 
 | // static | 
 | ProtoType ProtoType::Numeric(uint16_t size, bool is_signed) { | 
 |   PERFETTO_CHECK(size == 32 || size == 64); | 
 |   return {NUMERIC, size, is_signed}; | 
 | } | 
 |  | 
 | // static | 
 | ProtoType ProtoType::FromDescriptor( | 
 |     google::protobuf::FieldDescriptor::Type type) { | 
 |   if (type == google::protobuf::FieldDescriptor::Type::TYPE_UINT64) | 
 |     return Numeric(64, false); | 
 |  | 
 |   if (type == google::protobuf::FieldDescriptor::Type::TYPE_INT64) | 
 |     return Numeric(64, true); | 
 |  | 
 |   if (type == google::protobuf::FieldDescriptor::Type::TYPE_UINT32) | 
 |     return Numeric(32, false); | 
 |  | 
 |   if (type == google::protobuf::FieldDescriptor::Type::TYPE_INT32) | 
 |     return Numeric(32, true); | 
 |  | 
 |   if (type == google::protobuf::FieldDescriptor::Type::TYPE_STRING) | 
 |     return String(); | 
 |  | 
 |   return Invalid(); | 
 | } | 
 |  | 
 | ProtoType GetCommon(ProtoType one, ProtoType other) { | 
 |   // Always need to prefer the LHS as it is the one already present | 
 |   // in the proto. | 
 |   if (one.type == ProtoType::STRING) | 
 |     return ProtoType::String(); | 
 |  | 
 |   if (one.is_signed || other.is_signed) { | 
 |     one = one.GetSigned(); | 
 |     other = other.GetSigned(); | 
 |   } | 
 |  | 
 |   return ProtoType::Numeric(std::max(one.size, other.size), one.is_signed); | 
 | } | 
 |  | 
 | ProtoType InferProtoType(const FtraceEvent::Field& field) { | 
 |   // Fixed length strings: "char foo[16]" | 
 |   if (std::regex_match(field.type_and_name, std::regex(R"(char \w+\[\d+\])"))) | 
 |     return ProtoType::String(); | 
 |  | 
 |   // String pointers: "__data_loc char[] foo" (as in | 
 |   // 'cpufreq_interactive_boost'). | 
 |   if (Contains(field.type_and_name, "char[] ")) | 
 |     return ProtoType::String(); | 
 |   if (Contains(field.type_and_name, "char * ")) | 
 |     return ProtoType::String(); | 
 |  | 
 |   // Variable length strings: "char* foo" | 
 |   if (StartsWith(field.type_and_name, "char *")) | 
 |     return ProtoType::String(); | 
 |  | 
 |   // Variable length strings: "char foo" + size: 0 (as in 'print'). | 
 |   if (StartsWith(field.type_and_name, "char ") && field.size == 0) | 
 |     return ProtoType::String(); | 
 |  | 
 |   // ino_t, i_ino and dev_t are 32bit on some devices 64bit on others. For the | 
 |   // protos we need to choose the largest possible size. | 
 |   if (StartsWith(field.type_and_name, "ino_t ") || | 
 |       StartsWith(field.type_and_name, "i_ino ") || | 
 |       StartsWith(field.type_and_name, "dev_t ")) { | 
 |     return ProtoType::Numeric(64, /* is_signed= */ false); | 
 |   } | 
 |  | 
 |   // Ints of various sizes: | 
 |   if (field.size <= 4) | 
 |     return ProtoType::Numeric(32, field.is_signed); | 
 |   if (field.size <= 8) | 
 |     return ProtoType::Numeric(64, field.is_signed); | 
 |   return ProtoType::Invalid(); | 
 | } | 
 |  | 
 | Proto::Proto(std::string evt_name, const google::protobuf::Descriptor& desc) | 
 |     : name(desc.name()), event_name(evt_name) { | 
 |   for (int i = 0; i < desc.field_count(); ++i) { | 
 |     const google::protobuf::FieldDescriptor* field = desc.field(i); | 
 |     PERFETTO_CHECK(field); | 
 |     AddField(Field{ProtoType::FromDescriptor(field->type()), field->name(), | 
 |                    uint32_t(field->number())}); | 
 |   } | 
 | } | 
 |  | 
 | std::vector<const Proto::Field*> Proto::SortedFields() { | 
 |   std::vector<const Proto::Field*> sorted_fields; | 
 |  | 
 |   for (const auto& p : fields) { | 
 |     sorted_fields.emplace_back(&p.second); | 
 |   } | 
 |   std::sort(sorted_fields.begin(), sorted_fields.end(), | 
 |             [](const Proto::Field* a, const Proto::Field* b) { | 
 |               return a->number < b->number; | 
 |             }); | 
 |   return sorted_fields; | 
 | } | 
 |  | 
 | std::string Proto::ToString() { | 
 |   std::string s; | 
 |   s += "message " + name + " {\n"; | 
 |   for (const auto field : SortedFields()) { | 
 |     s += "  optional " + field->type.ToString() + " " + field->name + " = " + | 
 |          std::to_string(field->number) + ";\n"; | 
 |   } | 
 |   s += "}\n"; | 
 |   return s; | 
 | } | 
 |  | 
 | void Proto::MergeFrom(const Proto& other) { | 
 |   // Always keep number from the left hand side. | 
 |   PERFETTO_CHECK(name == other.name); | 
 |   for (const auto& p : other.fields) { | 
 |     auto it = fields.find(p.first); | 
 |     if (it == fields.end()) { | 
 |       Proto::Field field = p.second; | 
 |       field.number = ++max_id; | 
 |       AddField(std::move(field)); | 
 |     } else { | 
 |       it->second.type = GetCommon(it->second.type, p.second.type); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void Proto::AddField(Proto::Field other) { | 
 |   max_id = std::max(max_id, other.number); | 
 |   fields.emplace(other.name, std::move(other)); | 
 | } | 
 |  | 
 | }  // namespace perfetto |