| /* |
| * 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 <stdint.h> |
| |
| #include "perfetto/base/status.h" |
| #include "perfetto/protozero/proto_decoder.h" |
| #include "perfetto/protozero/proto_utils.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 { |
| |
| template <protozero::proto_utils::ProtoWireType wire_type, typename cpp_type> |
| using PRFI = protozero::PackedRepeatedFieldIterator<wire_type, cpp_type>; |
| |
| void AppendProtoType(std::string& target, const std::string& value) { |
| if (!target.empty()) |
| target += '.'; |
| target += value; |
| } |
| |
| } // 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_ = std::nullopt; |
| other.old_key_length_ = std::nullopt; |
| } |
| |
| ProtoToArgsParser::ScopedNestedKeyContext::~ScopedNestedKeyContext() { |
| RemoveFieldSuffix(); |
| } |
| |
| void ProtoToArgsParser::ScopedNestedKeyContext::RemoveFieldSuffix() { |
| 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_ = std::nullopt; |
| old_key_length_ = std::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, |
| int* unknown_extensions) { |
| ScopedNestedKeyContext key_context(key_prefix_); |
| return ParseMessageInternal(key_context, cb, type, allowed_fields, delegate, |
| unknown_extensions); |
| } |
| |
| base::Status ProtoToArgsParser::ParseMessageInternal( |
| ScopedNestedKeyContext& key_context, |
| const protozero::ConstBytes& cb, |
| const std::string& type, |
| const std::vector<uint16_t>* allowed_fields, |
| Delegate& delegate, |
| int* unknown_extensions) { |
| if (auto override_result = |
| MaybeApplyOverrideForType(type, key_context, cb, delegate)) { |
| return override_result.value(); |
| } |
| |
| 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; |
| bool empty_message = true; |
| protozero::ProtoDecoder decoder(cb); |
| for (protozero::Field f = decoder.ReadField(); f.valid(); |
| f = decoder.ReadField()) { |
| empty_message = false; |
| auto field = descriptor.FindFieldByTag(f.id()); |
| if (!field) { |
| if (unknown_extensions != nullptr) { |
| (*unknown_extensions)++; |
| } |
| // 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; |
| } |
| |
| // Packed fields need to be handled specially because |
| if (field->is_packed()) { |
| RETURN_IF_ERROR(ParsePackedField(*field, repeated_field_index, f, |
| delegate, unknown_extensions)); |
| continue; |
| } |
| |
| RETURN_IF_ERROR(ParseField(*field, repeated_field_index[f.id()], f, |
| delegate, unknown_extensions)); |
| if (field->is_repeated()) { |
| repeated_field_index[f.id()]++; |
| } |
| } |
| |
| if (empty_message) { |
| delegate.AddNull(key_prefix_); |
| } |
| |
| return base::OkStatus(); |
| } |
| |
| base::Status ProtoToArgsParser::ParseField( |
| const FieldDescriptor& field_descriptor, |
| int repeated_field_number, |
| protozero::Field field, |
| Delegate& delegate, |
| int* unknown_extensions) { |
| 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. |
| ScopedNestedKeyContext key_context(key_prefix_); |
| AppendProtoType(key_prefix_.flat_key, field_descriptor.name()); |
| AppendProtoType(key_prefix_.key, prefix_part); |
| |
| // If we have an override parser then use that instead and move onto the |
| // next loop. |
| if (std::optional<base::Status> status = |
| MaybeApplyOverrideForField(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 ParseMessageInternal(key_context, field.as_bytes(), |
| field_descriptor.resolved_type_name(), nullptr, |
| delegate, unknown_extensions); |
| } |
| return ParseSimpleField(field_descriptor, field, delegate); |
| } |
| |
| base::Status ProtoToArgsParser::ParsePackedField( |
| const FieldDescriptor& field_descriptor, |
| std::unordered_map<size_t, int>& repeated_field_index, |
| protozero::Field field, |
| Delegate& delegate, |
| int* unknown_extensions) { |
| using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto; |
| using PWT = protozero::proto_utils::ProtoWireType; |
| |
| if (!field_descriptor.is_repeated()) { |
| return base::ErrStatus("Packed field %s must be repeated", |
| field_descriptor.name().c_str()); |
| } |
| if (field.type() != PWT::kLengthDelimited) { |
| return base::ErrStatus( |
| "Packed field %s must have a length delimited wire type", |
| field_descriptor.name().c_str()); |
| } |
| |
| auto parse = [&](uint64_t new_value, PWT wire_type) { |
| protozero::Field f; |
| f.initialize(field.id(), static_cast<uint8_t>(wire_type), new_value, 0); |
| return ParseField(field_descriptor, repeated_field_index[field.id()]++, f, |
| delegate, unknown_extensions); |
| }; |
| |
| const uint8_t* data = field.as_bytes().data; |
| size_t size = field.as_bytes().size; |
| bool perr = false; |
| switch (field_descriptor.type()) { |
| case FieldDescriptorProto::TYPE_INT32: |
| case FieldDescriptorProto::TYPE_INT64: |
| case FieldDescriptorProto::TYPE_UINT32: |
| case FieldDescriptorProto::TYPE_UINT64: |
| case FieldDescriptorProto::TYPE_ENUM: |
| for (PRFI<PWT::kVarInt, uint64_t> it(data, size, &perr); it; ++it) { |
| parse(*it, PWT::kVarInt); |
| } |
| break; |
| case FieldDescriptorProto::TYPE_FIXED32: |
| case FieldDescriptorProto::TYPE_SFIXED32: |
| case FieldDescriptorProto::TYPE_FLOAT: |
| for (PRFI<PWT::kFixed32, uint32_t> it(data, size, &perr); it; ++it) { |
| parse(*it, PWT::kFixed32); |
| } |
| break; |
| case FieldDescriptorProto::TYPE_FIXED64: |
| case FieldDescriptorProto::TYPE_SFIXED64: |
| case FieldDescriptorProto::TYPE_DOUBLE: |
| for (PRFI<PWT::kFixed64, uint64_t> it(data, size, &perr); it; ++it) { |
| parse(*it, PWT::kFixed64); |
| } |
| break; |
| default: |
| return base::ErrStatus("Unsupported packed repeated field"); |
| } |
| return base::OkStatus(); |
| } |
| |
| void ProtoToArgsParser::AddParsingOverrideForField( |
| const std::string& field, |
| ParsingOverrideForField func) { |
| field_overrides_[field] = std::move(func); |
| } |
| |
| void ProtoToArgsParser::AddParsingOverrideForType(const std::string& type, |
| ParsingOverrideForType func) { |
| type_overrides_[type] = std::move(func); |
| } |
| |
| std::optional<base::Status> ProtoToArgsParser::MaybeApplyOverrideForField( |
| const protozero::Field& field, |
| Delegate& delegate) { |
| auto it = field_overrides_.find(key_prefix_.flat_key); |
| if (it == field_overrides_.end()) |
| return std::nullopt; |
| return it->second(field, delegate); |
| } |
| |
| std::optional<base::Status> ProtoToArgsParser::MaybeApplyOverrideForType( |
| const std::string& message_type, |
| ScopedNestedKeyContext& key, |
| const protozero::ConstBytes& data, |
| Delegate& delegate) { |
| auto it = type_overrides_.find(message_type); |
| if (it == type_overrides_.end()) |
| return std::nullopt; |
| return it->second(key, data, 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: |
| 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: |
| 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: |
| case FieldDescriptorProto::TYPE_FIXED32: |
| delegate.AddUnsignedInteger(key_prefix_, field.as_uint32()); |
| return base::OkStatus(); |
| case FieldDescriptorProto::TYPE_UINT64: |
| case FieldDescriptorProto::TYPE_FIXED64: |
| 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_); |
| AppendProtoType(key_prefix_.key, name); |
| AppendProtoType(key_prefix_.flat_key, name); |
| return context; |
| } |
| |
| } // namespace util |
| } // namespace trace_processor |
| } // namespace perfetto |