1 /*
2 * Copyright (C) 2021 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/trace_processor/util/proto_to_args_parser.h"
18
19 #include "protos/perfetto/common/descriptor.pbzero.h"
20 #include "src/trace_processor/util/descriptors.h"
21 #include "src/trace_processor/util/status_macros.h"
22
23 namespace perfetto {
24 namespace trace_processor {
25 namespace util {
26
27 namespace {
28
29 // ScopedStringAppender will add |append| to |dest| when constructed and
30 // erases the appended suffix from |dest| when it goes out of scope. Thus
31 // |dest| must be valid for the entire lifetime of ScopedStringAppender.
32 //
33 // This is useful as we descend into a proto since the column names just
34 // appended with ".field_name" as we go lower.
35 //
36 // I.E. message1.message2.field_name1 is a column, but we'll then need to
37 // append message1.message2.field_name2 afterwards so we only need to append
38 // "field_name1" within some scope.
39 class ScopedStringAppender {
40 public:
ScopedStringAppender(const std::string & append,std::string * dest)41 ScopedStringAppender(const std::string& append, std::string* dest)
42 : old_size_(dest->size()), dest_(dest) {
43 if (dest->empty()) {
44 dest_->reserve(append.size());
45 } else {
46 dest_->reserve(old_size_ + 1 + append.size());
47 dest_->append(".");
48 }
49 dest_->append(append);
50 }
~ScopedStringAppender()51 ~ScopedStringAppender() { dest_->erase(old_size_); }
52
53 private:
54 size_t old_size_;
55 std::string* dest_;
56 };
57
58 } // namespace
59
60 ProtoToArgsParser::Key::Key() = default;
Key(const std::string & k)61 ProtoToArgsParser::Key::Key(const std::string& k) : flat_key(k), key(k) {}
Key(const std::string & fk,const std::string & k)62 ProtoToArgsParser::Key::Key(const std::string& fk, const std::string& k)
63 : flat_key(fk), key(k) {}
64 ProtoToArgsParser::Key::~Key() = default;
65
66 ProtoToArgsParser::Delegate::~Delegate() = default;
67
ProtoToArgsParser(const DescriptorPool & pool)68 ProtoToArgsParser::ProtoToArgsParser(const DescriptorPool& pool) : pool_(pool) {
69 constexpr int kDefaultSize = 64;
70 key_prefix_.key.reserve(kDefaultSize);
71 key_prefix_.flat_key.reserve(kDefaultSize);
72 }
73
ParseMessage(const protozero::ConstBytes & cb,const std::string & type,const std::vector<uint16_t> * allowed_fields,Delegate & delegate)74 base::Status ProtoToArgsParser::ParseMessage(
75 const protozero::ConstBytes& cb,
76 const std::string& type,
77 const std::vector<uint16_t>* allowed_fields,
78 Delegate& delegate) {
79 auto idx = pool_.FindDescriptorIdx(type);
80 if (!idx) {
81 return base::Status("Failed to find proto descriptor");
82 }
83
84 auto& descriptor = pool_.descriptors()[*idx];
85
86 std::unordered_map<size_t, int> repeated_field_index;
87
88 protozero::ProtoDecoder decoder(cb);
89 for (protozero::Field f = decoder.ReadField(); f.valid();
90 f = decoder.ReadField()) {
91 auto field = descriptor.FindFieldByTag(f.id());
92 if (!field) {
93 // Unknown field, possibly an unknown extension.
94 continue;
95 }
96
97 // If allowlist is not provided, reflect all fields. Otherwise, check if the
98 // current field either an extension or is in allowlist.
99 bool is_allowed = field->is_extension() || !allowed_fields ||
100 std::find(allowed_fields->begin(), allowed_fields->end(),
101 f.id()) != allowed_fields->end();
102
103 if (!is_allowed) {
104 // Field is neither an extension, nor is allowed to be
105 // reflected.
106 continue;
107 }
108 RETURN_IF_ERROR(
109 ParseField(*field, repeated_field_index[f.id()], f, delegate));
110 if (field->is_repeated()) {
111 repeated_field_index[f.id()]++;
112 }
113 }
114
115 return base::OkStatus();
116 }
117
ParseField(const FieldDescriptor & field_descriptor,int repeated_field_number,protozero::Field field,Delegate & delegate)118 base::Status ProtoToArgsParser::ParseField(
119 const FieldDescriptor& field_descriptor,
120 int repeated_field_number,
121 protozero::Field field,
122 Delegate& delegate) {
123 std::string prefix_part = field_descriptor.name();
124 if (field_descriptor.is_repeated()) {
125 std::string number = std::to_string(repeated_field_number);
126 prefix_part.reserve(prefix_part.length() + number.length() + 2);
127 prefix_part.append("[");
128 prefix_part.append(number);
129 prefix_part.append("]");
130 }
131
132 // In the args table we build up message1.message2.field1 as the column
133 // name. This will append the ".field1" suffix to |key_prefix| and then
134 // remove it when it goes out of scope.
135 ScopedStringAppender scoped_prefix(prefix_part, &key_prefix_.key);
136 ScopedStringAppender scoped_flat_key_prefix(field_descriptor.name(),
137 &key_prefix_.flat_key);
138
139 // If we have an override parser then use that instead and move onto the
140 // next loop.
141 if (base::Optional<base::Status> status =
142 MaybeApplyOverride(field, delegate)) {
143 return *status;
144 }
145
146 // If this is not a message we can just immediately add the column name and
147 // get the value out of |field|. However if it is a message we need to
148 // recurse into it.
149 if (field_descriptor.type() ==
150 protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) {
151 return ParseMessage(field.as_bytes(), field_descriptor.resolved_type_name(),
152 nullptr, delegate);
153 }
154
155 return ParseSimpleField(field_descriptor, field, delegate);
156 }
157
AddParsingOverride(std::string field,ParsingOverride func)158 void ProtoToArgsParser::AddParsingOverride(std::string field,
159 ParsingOverride func) {
160 overrides_[std::move(field)] = std::move(func);
161 }
162
MaybeApplyOverride(const protozero::Field & field,Delegate & delegate)163 base::Optional<base::Status> ProtoToArgsParser::MaybeApplyOverride(
164 const protozero::Field& field,
165 Delegate& delegate) {
166 auto it = overrides_.find(key_prefix_.flat_key);
167 if (it == overrides_.end())
168 return base::nullopt;
169 return it->second(field, delegate);
170 }
171
ParseSimpleField(const FieldDescriptor & descriptor,const protozero::Field & field,Delegate & delegate)172 base::Status ProtoToArgsParser::ParseSimpleField(
173 const FieldDescriptor& descriptor,
174 const protozero::Field& field,
175 Delegate& delegate) {
176 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
177 switch (descriptor.type()) {
178 case FieldDescriptorProto::TYPE_INT32:
179 case FieldDescriptorProto::TYPE_SFIXED32:
180 case FieldDescriptorProto::TYPE_FIXED32:
181 delegate.AddInteger(key_prefix_, field.as_int32());
182 return base::OkStatus();
183 case FieldDescriptorProto::TYPE_SINT32:
184 delegate.AddInteger(key_prefix_, field.as_sint32());
185 return base::OkStatus();
186 case FieldDescriptorProto::TYPE_INT64:
187 case FieldDescriptorProto::TYPE_SFIXED64:
188 case FieldDescriptorProto::TYPE_FIXED64:
189 delegate.AddInteger(key_prefix_, field.as_int64());
190 return base::OkStatus();
191 case FieldDescriptorProto::TYPE_SINT64:
192 delegate.AddInteger(key_prefix_, field.as_sint64());
193 return base::OkStatus();
194 case FieldDescriptorProto::TYPE_UINT32:
195 delegate.AddUnsignedInteger(key_prefix_, field.as_uint32());
196 return base::OkStatus();
197 case FieldDescriptorProto::TYPE_UINT64:
198 delegate.AddUnsignedInteger(key_prefix_, field.as_uint64());
199 return base::OkStatus();
200 case FieldDescriptorProto::TYPE_BOOL:
201 delegate.AddBoolean(key_prefix_, field.as_bool());
202 return base::OkStatus();
203 case FieldDescriptorProto::TYPE_DOUBLE:
204 delegate.AddDouble(key_prefix_, field.as_double());
205 return base::OkStatus();
206 case FieldDescriptorProto::TYPE_FLOAT:
207 delegate.AddDouble(key_prefix_, static_cast<double>(field.as_float()));
208 return base::OkStatus();
209 case FieldDescriptorProto::TYPE_STRING:
210 delegate.AddString(key_prefix_, field.as_string());
211 return base::OkStatus();
212 case FieldDescriptorProto::TYPE_ENUM: {
213 auto opt_enum_descriptor_idx =
214 pool_.FindDescriptorIdx(descriptor.resolved_type_name());
215 if (!opt_enum_descriptor_idx) {
216 delegate.AddInteger(key_prefix_, field.as_int32());
217 return base::OkStatus();
218 }
219 auto opt_enum_string =
220 pool_.descriptors()[*opt_enum_descriptor_idx].FindEnumString(
221 field.as_int32());
222 if (!opt_enum_string) {
223 // Fall back to the integer representation of the field.
224 delegate.AddInteger(key_prefix_, field.as_int32());
225 return base::OkStatus();
226 }
227 delegate.AddString(key_prefix_,
228 protozero::ConstChars{opt_enum_string->data(),
229 opt_enum_string->size()});
230 return base::OkStatus();
231 }
232 default:
233 return base::ErrStatus(
234 "Tried to write value of type field %s (in proto type "
235 "%s) which has type enum %d",
236 descriptor.name().c_str(), descriptor.resolved_type_name().c_str(),
237 descriptor.type());
238 }
239 }
240
241 } // namespace util
242 } // namespace trace_processor
243 } // namespace perfetto
244