| /* |
| * Copyright (C) 2021 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/trace_processor/util/proto_to_args_parser.h" |
| |
| #include "protos/perfetto/common/descriptor.pbzero.h" |
| #include "src/trace_processor/util/descriptors.h" |
| #include "src/trace_processor/util/status_macros.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| namespace util { |
| |
| namespace { |
| |
| // ScopedStringAppender will add |append| to |dest| when constructed and |
| // erases the appended suffix from |dest| when it goes out of scope. Thus |
| // |dest| must be valid for the entire lifetime of ScopedStringAppender. |
| // |
| // This is useful as we descend into a proto since the column names just |
| // appended with ".field_name" as we go lower. |
| // |
| // I.E. message1.message2.field_name1 is a column, but we'll then need to |
| // append message1.message2.field_name2 afterwards so we only need to append |
| // "field_name1" within some scope. |
| class ScopedStringAppender { |
| public: |
| ScopedStringAppender(const std::string& append, std::string* dest) |
| : old_size_(dest->size()), dest_(dest) { |
| if (dest->empty()) { |
| dest_->reserve(append.size()); |
| } else { |
| dest_->reserve(old_size_ + 1 + append.size()); |
| dest_->append("."); |
| } |
| dest_->append(append); |
| } |
| ~ScopedStringAppender() { dest_->erase(old_size_); } |
| |
| private: |
| size_t old_size_; |
| std::string* dest_; |
| }; |
| |
| } // namespace |
| |
| ProtoToArgsParser::Key::Key() = default; |
| ProtoToArgsParser::Key::Key(const std::string& k) : flat_key(k), key(k) {} |
| ProtoToArgsParser::Key::Key(const std::string& fk, const std::string& k) |
| : flat_key(fk), key(k) {} |
| ProtoToArgsParser::Key::~Key() = default; |
| |
| ProtoToArgsParser::ScopedNestedKeyContext::ScopedNestedKeyContext(Key& key) |
| : key_(key), |
| old_flat_key_length_(key.flat_key.length()), |
| old_key_length_(key.key.length()) {} |
| |
| ProtoToArgsParser::ScopedNestedKeyContext::ScopedNestedKeyContext( |
| ProtoToArgsParser::ScopedNestedKeyContext&& other) |
| : key_(other.key_), |
| old_flat_key_length_(other.old_flat_key_length_), |
| old_key_length_(other.old_key_length_) { |
| other.old_flat_key_length_ = base::nullopt; |
| other.old_key_length_ = base::nullopt; |
| } |
| |
| ProtoToArgsParser::ScopedNestedKeyContext::~ScopedNestedKeyContext() { |
| Reset(); |
| } |
| |
| void ProtoToArgsParser::ScopedNestedKeyContext::Reset() { |
| if (old_flat_key_length_) |
| key_.flat_key.resize(old_flat_key_length_.value()); |
| if (old_key_length_) |
| key_.key.resize(old_key_length_.value()); |
| old_flat_key_length_ = base::nullopt; |
| old_key_length_ = base::nullopt; |
| } |
| |
| ProtoToArgsParser::Delegate::~Delegate() = default; |
| |
| ProtoToArgsParser::ProtoToArgsParser(const DescriptorPool& pool) : pool_(pool) { |
| constexpr int kDefaultSize = 64; |
| key_prefix_.key.reserve(kDefaultSize); |
| key_prefix_.flat_key.reserve(kDefaultSize); |
| } |
| |
| base::Status ProtoToArgsParser::ParseMessage( |
| const protozero::ConstBytes& cb, |
| const std::string& type, |
| const std::vector<uint16_t>* allowed_fields, |
| Delegate& delegate) { |
| auto idx = pool_.FindDescriptorIdx(type); |
| if (!idx) { |
| return base::Status("Failed to find proto descriptor"); |
| } |
| |
| auto& descriptor = pool_.descriptors()[*idx]; |
| |
| std::unordered_map<size_t, int> repeated_field_index; |
| |
| protozero::ProtoDecoder decoder(cb); |
| for (protozero::Field f = decoder.ReadField(); f.valid(); |
| f = decoder.ReadField()) { |
| auto field = descriptor.FindFieldByTag(f.id()); |
| if (!field) { |
| // Unknown field, possibly an unknown extension. |
| continue; |
| } |
| |
| // If allowlist is not provided, reflect all fields. Otherwise, check if the |
| // current field either an extension or is in allowlist. |
| bool is_allowed = field->is_extension() || !allowed_fields || |
| std::find(allowed_fields->begin(), allowed_fields->end(), |
| f.id()) != allowed_fields->end(); |
| |
| if (!is_allowed) { |
| // Field is neither an extension, nor is allowed to be |
| // reflected. |
| continue; |
| } |
| RETURN_IF_ERROR( |
| ParseField(*field, repeated_field_index[f.id()], f, delegate)); |
| if (field->is_repeated()) { |
| repeated_field_index[f.id()]++; |
| } |
| } |
| |
| return base::OkStatus(); |
| } |
| |
| base::Status ProtoToArgsParser::ParseField( |
| const FieldDescriptor& field_descriptor, |
| int repeated_field_number, |
| protozero::Field field, |
| Delegate& delegate) { |
| std::string prefix_part = field_descriptor.name(); |
| if (field_descriptor.is_repeated()) { |
| std::string number = std::to_string(repeated_field_number); |
| prefix_part.reserve(prefix_part.length() + number.length() + 2); |
| prefix_part.append("["); |
| prefix_part.append(number); |
| prefix_part.append("]"); |
| } |
| |
| // In the args table we build up message1.message2.field1 as the column |
| // name. This will append the ".field1" suffix to |key_prefix| and then |
| // remove it when it goes out of scope. |
| ScopedStringAppender scoped_prefix(prefix_part, &key_prefix_.key); |
| ScopedStringAppender scoped_flat_key_prefix(field_descriptor.name(), |
| &key_prefix_.flat_key); |
| |
| // If we have an override parser then use that instead and move onto the |
| // next loop. |
| if (base::Optional<base::Status> status = |
| MaybeApplyOverride(field, delegate)) { |
| return *status; |
| } |
| |
| // If this is not a message we can just immediately add the column name and |
| // get the value out of |field|. However if it is a message we need to |
| // recurse into it. |
| if (field_descriptor.type() == |
| protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) { |
| return ParseMessage(field.as_bytes(), field_descriptor.resolved_type_name(), |
| nullptr, delegate); |
| } |
| |
| return ParseSimpleField(field_descriptor, field, delegate); |
| } |
| |
| void ProtoToArgsParser::AddParsingOverride(std::string field, |
| ParsingOverride func) { |
| overrides_[std::move(field)] = std::move(func); |
| } |
| |
| base::Optional<base::Status> ProtoToArgsParser::MaybeApplyOverride( |
| const protozero::Field& field, |
| Delegate& delegate) { |
| auto it = overrides_.find(key_prefix_.flat_key); |
| if (it == overrides_.end()) |
| return base::nullopt; |
| return it->second(field, delegate); |
| } |
| |
| base::Status ProtoToArgsParser::ParseSimpleField( |
| const FieldDescriptor& descriptor, |
| const protozero::Field& field, |
| Delegate& delegate) { |
| using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto; |
| switch (descriptor.type()) { |
| case FieldDescriptorProto::TYPE_INT32: |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| case FieldDescriptorProto::TYPE_FIXED32: |
| delegate.AddInteger(key_prefix_, field.as_int32()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_SINT32: |
| delegate.AddInteger(key_prefix_, field.as_sint32()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_INT64: |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| case FieldDescriptorProto::TYPE_FIXED64: |
| delegate.AddInteger(key_prefix_, field.as_int64()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_SINT64: |
| delegate.AddInteger(key_prefix_, field.as_sint64()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_UINT32: |
| delegate.AddUnsignedInteger(key_prefix_, field.as_uint32()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_UINT64: |
| delegate.AddUnsignedInteger(key_prefix_, field.as_uint64()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_BOOL: |
| delegate.AddBoolean(key_prefix_, field.as_bool()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| delegate.AddDouble(key_prefix_, field.as_double()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_FLOAT: |
| delegate.AddDouble(key_prefix_, static_cast<double>(field.as_float())); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_STRING: |
| delegate.AddString(key_prefix_, field.as_string()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_ENUM: { |
| auto opt_enum_descriptor_idx = |
| pool_.FindDescriptorIdx(descriptor.resolved_type_name()); |
| if (!opt_enum_descriptor_idx) { |
| delegate.AddInteger(key_prefix_, field.as_int32()); |
| return base::OkStatus(); |
| } |
| auto opt_enum_string = |
| pool_.descriptors()[*opt_enum_descriptor_idx].FindEnumString( |
| field.as_int32()); |
| if (!opt_enum_string) { |
| // Fall back to the integer representation of the field. |
| delegate.AddInteger(key_prefix_, field.as_int32()); |
| return base::OkStatus(); |
| } |
| delegate.AddString(key_prefix_, |
| protozero::ConstChars{opt_enum_string->data(), |
| opt_enum_string->size()}); |
| return base::OkStatus(); |
| } |
| default: |
| return base::ErrStatus( |
| "Tried to write value of type field %s (in proto type " |
| "%s) which has type enum %d", |
| descriptor.name().c_str(), descriptor.resolved_type_name().c_str(), |
| descriptor.type()); |
| } |
| } |
| |
| ProtoToArgsParser::ScopedNestedKeyContext ProtoToArgsParser::EnterArray( |
| size_t index) { |
| auto context = ScopedNestedKeyContext(key_prefix_); |
| key_prefix_.key += "[" + std::to_string(index) + "]"; |
| return context; |
| } |
| |
| ProtoToArgsParser::ScopedNestedKeyContext ProtoToArgsParser::EnterDictionary( |
| const std::string& name) { |
| auto context = ScopedNestedKeyContext(key_prefix_); |
| if (!key_prefix_.key.empty()) |
| key_prefix_.key += '.'; |
| key_prefix_.key += name; |
| if (!key_prefix_.flat_key.empty()) |
| key_prefix_.flat_key += '.'; |
| key_prefix_.flat_key += name; |
| return context; |
| } |
| |
| } // namespace util |
| } // namespace trace_processor |
| } // namespace perfetto |