Merge "processor: Parse TrackEvent arguments"
diff --git a/include/perfetto/ext/base/string_writer.h b/include/perfetto/ext/base/string_writer.h
index 10e92a0..6bc7fad 100644
--- a/include/perfetto/ext/base/string_writer.h
+++ b/include/perfetto/ext/base/string_writer.h
@@ -68,34 +68,20 @@
   // digits of the integer is less than |padding|.
   template <char padchar, uint64_t padding>
   void AppendPaddedInt(int64_t sign_value) {
-    // Need to add 2 to the number of digits to account for minus sign and
-    // rounding down of digits10.
-    constexpr auto kMaxDigits = std::numeric_limits<uint64_t>::digits10 + 2;
-    constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding;
-    PERFETTO_DCHECK(pos_ + kSizeNeeded <= size_);
-
-    char data[kSizeNeeded];
     const bool negate = signbit(static_cast<double>(sign_value));
-    uint64_t value = static_cast<uint64_t>(std::abs(sign_value));
+    uint64_t absolute_value = static_cast<uint64_t>(std::abs(sign_value));
+    AppendPaddedInt<padchar, padding>(absolute_value, negate);
+  }
 
-    size_t idx;
-    for (idx = kSizeNeeded - 1; value >= 10;) {
-      char digit = value % 10;
-      value /= 10;
-      data[idx--] = digit + '0';
-    }
-    data[idx--] = static_cast<char>(value) + '0';
+  void AppendUnsignedInt(uint64_t value) {
+    AppendPaddedUnsignedInt<'0', 0>(value);
+  }
 
-    if (padding > 0) {
-      size_t num_digits = kSizeNeeded - 1 - idx;
-      for (size_t i = num_digits; i < padding; i++) {
-        data[idx--] = padchar;
-      }
-    }
-
-    if (negate)
-      buffer_[pos_++] = '-';
-    AppendString(&data[idx + 1], kSizeNeeded - idx - 1);
+  // Appends an unsigned integer to the buffer, padding with |padchar| if the
+  // number of digits of the integer is less than |padding|.
+  template <char padchar, uint64_t padding>
+  void AppendPaddedUnsignedInt(uint64_t value) {
+    AppendPaddedInt<padchar, padding>(value, false);
   }
 
   // Appends a hex integer to the buffer.
@@ -118,6 +104,14 @@
     pos_ += res;
   }
 
+  void AppendBool(bool value) {
+    if (value) {
+      AppendLiteral("true");
+      return;
+    }
+    AppendLiteral("false");
+  }
+
   StringView GetStringView() {
     PERFETTO_DCHECK(pos_ <= size_);
     return StringView(buffer_, pos_);
@@ -137,6 +131,36 @@
   void reset() { pos_ = 0; }
 
  private:
+  template <char padchar, uint64_t padding>
+  void AppendPaddedInt(uint64_t absolute_value, bool negate) {
+    // Need to add 2 to the number of digits to account for minus sign and
+    // rounding down of digits10.
+    constexpr auto kMaxDigits = std::numeric_limits<uint64_t>::digits10 + 2;
+    constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding;
+    PERFETTO_DCHECK(pos_ + kSizeNeeded <= size_);
+
+    char data[kSizeNeeded];
+
+    size_t idx;
+    for (idx = kSizeNeeded - 1; absolute_value >= 10;) {
+      char digit = absolute_value % 10;
+      absolute_value /= 10;
+      data[idx--] = digit + '0';
+    }
+    data[idx--] = static_cast<char>(absolute_value) + '0';
+
+    if (padding > 0) {
+      size_t num_digits = kSizeNeeded - 1 - idx;
+      for (size_t i = num_digits; i < padding; i++) {
+        data[idx--] = padchar;
+      }
+    }
+
+    if (negate)
+      buffer_[pos_++] = '-';
+    AppendString(&data[idx + 1], kSizeNeeded - idx - 1);
+  }
+
   char* buffer_ = nullptr;
   size_t size_ = 0;
   size_t pos_ = 0;
diff --git a/src/base/string_writer_unittest.cc b/src/base/string_writer_unittest.cc
index 8d83dcc..12a7872 100644
--- a/src/base/string_writer_unittest.cc
+++ b/src/base/string_writer_unittest.cc
@@ -37,6 +37,11 @@
   }
   {
     base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendUnsignedInt(523);
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "523");
+  }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
     writer.AppendPaddedInt<'0', 3>(0);
     ASSERT_EQ(writer.GetStringView().ToStdString(), "000");
   }
@@ -62,6 +67,11 @@
   }
   {
     base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendPaddedUnsignedInt<' ', 5>(123);
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "  123");
+  }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
     writer.AppendDouble(123.25);
     ASSERT_EQ(writer.GetStringView().ToStdString(), "123.250000");
   }
@@ -75,6 +85,21 @@
     writer.AppendInt(std::numeric_limits<int64_t>::max());
     ASSERT_EQ(writer.GetStringView().ToStdString(), "9223372036854775807");
   }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendUnsignedInt(std::numeric_limits<uint64_t>::max());
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "18446744073709551615");
+  }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendBool(true);
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "true");
+  }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendBool(false);
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "false");
+  }
 
   constexpr char kTestStr[] = "test";
   {
@@ -105,13 +130,16 @@
   base::StringWriter writer(buffer, sizeof(buffer));
   writer.AppendChar('0');
   writer.AppendInt(132545);
+  writer.AppendUnsignedInt(523);
   writer.AppendPaddedInt<'0', 0>(1);
   writer.AppendPaddedInt<'0', 3>(0);
   writer.AppendPaddedInt<'0', 1>(1);
   writer.AppendPaddedInt<'0', 2>(1);
   writer.AppendPaddedInt<'0', 3>(1);
   writer.AppendPaddedInt<' ', 5>(123);
+  writer.AppendPaddedUnsignedInt<' ', 5>(456);
   writer.AppendDouble(123.25);
+  writer.AppendBool(true);
 
   constexpr char kTestStr[] = "test";
   writer.AppendLiteral(kTestStr);
@@ -119,7 +147,7 @@
   writer.AppendString(kTestStr);
 
   ASSERT_EQ(writer.GetStringView().ToStdString(),
-            "01325451000101001  123123.250000testtesttest");
+            "01325455231000101001  123  456123.250000truetesttesttest");
 }
 
 }  // namespace
diff --git a/src/trace_processor/args_table.cc b/src/trace_processor/args_table.cc
index 8b781f2..bc89ef0 100644
--- a/src/trace_processor/args_table.cc
+++ b/src/trace_processor/args_table.cc
@@ -79,14 +79,30 @@
     case Variadic::Type::kInt:
       sqlite_utils::ReportSqliteResult(ctx, value.int_value);
       break;
-    case Variadic::Type::kReal:
-      sqlite_utils::ReportSqliteResult(ctx, value.real_value);
+    case Variadic::Type::kUint:
+      // BEWARE: uint64 is handled as signed int64 for SQLite operations.
+      sqlite_utils::ReportSqliteResult(ctx,
+                                       static_cast<int64_t>(value.uint_value));
       break;
     case Variadic::Type::kString: {
       const char* str = storage_->GetString(value.string_value).c_str();
       sqlite3_result_text(ctx, str, -1, sqlite_utils::kSqliteStatic);
       break;
     }
+    case Variadic::Type::kReal:
+      sqlite_utils::ReportSqliteResult(ctx, value.real_value);
+      break;
+    case Variadic::Type::kPointer:
+      // BEWARE: pointers are handled as signed int64 for SQLite operations.
+      sqlite_utils::ReportSqliteResult(
+          ctx, static_cast<int64_t>(value.pointer_value));
+      break;
+    case Variadic::Type::kBool:
+      sqlite_utils::ReportSqliteResult(ctx, value.bool_value);
+      break;
+    case Variadic::Type::kJson:
+      sqlite_utils::ReportSqliteResult(ctx, value.json_value);
+      break;
   }
 }
 
@@ -110,13 +126,16 @@
           });
       break;
     }
-    case Variadic::Type::kReal: {
+    case Variadic::Type::kUint: {
       bool op_is_null = sqlite_utils::IsOpIsNull(op);
-      auto predicate = sqlite_utils::CreateNumericPredicate<double>(op, value);
+      // BEWARE: uint64 is handled as signed int64 for SQLite operations.
+      auto predicate = sqlite_utils::CreateNumericPredicate<int64_t>(op, value);
       index->FilterRows(
           [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
             const auto& arg = storage_->args().arg_values()[row];
-            return arg.type == type_ ? predicate(arg.real_value) : op_is_null;
+            return arg.type == type_
+                       ? predicate(static_cast<int64_t>(arg.uint_value))
+                       : op_is_null;
           });
       break;
     }
@@ -131,6 +150,50 @@
       });
       break;
     }
+    case Variadic::Type::kReal: {
+      bool op_is_null = sqlite_utils::IsOpIsNull(op);
+      auto predicate = sqlite_utils::CreateNumericPredicate<double>(op, value);
+      index->FilterRows(
+          [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
+            const auto& arg = storage_->args().arg_values()[row];
+            return arg.type == type_ ? predicate(arg.real_value) : op_is_null;
+          });
+      break;
+    }
+    case Variadic::Type::kPointer: {
+      bool op_is_null = sqlite_utils::IsOpIsNull(op);
+      // BEWARE: pointers are handled as signed int64 for SQLite operations.
+      auto predicate = sqlite_utils::CreateNumericPredicate<int64_t>(op, value);
+      index->FilterRows(
+          [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
+            const auto& arg = storage_->args().arg_values()[row];
+            return arg.type == type_
+                       ? predicate(static_cast<int64_t>(arg.pointer_value))
+                       : op_is_null;
+          });
+      break;
+    }
+    case Variadic::Type::kBool: {
+      bool op_is_null = sqlite_utils::IsOpIsNull(op);
+      auto predicate = sqlite_utils::CreateNumericPredicate<bool>(op, value);
+      index->FilterRows(
+          [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
+            const auto& arg = storage_->args().arg_values()[row];
+            return arg.type == type_ ? predicate(arg.bool_value) : op_is_null;
+          });
+      break;
+    }
+    case Variadic::Type::kJson: {
+      auto predicate = sqlite_utils::CreateStringPredicate(op, value);
+      index->FilterRows([this,
+                         &predicate](uint32_t row) PERFETTO_ALWAYS_INLINE {
+        const auto& arg = storage_->args().arg_values()[row];
+        return arg.type == type_
+                   ? predicate(storage_->GetString(arg.json_value).c_str())
+                   : predicate(nullptr);
+      });
+      break;
+    }
   }
 }
 
@@ -150,14 +213,32 @@
     switch (type_) {
       case Variadic::Type::kInt:
         return sqlite_utils::CompareValuesAsc(arg_f.int_value, arg_s.int_value);
-      case Variadic::Type::kReal:
-        return sqlite_utils::CompareValuesAsc(arg_f.real_value,
-                                              arg_s.real_value);
+      case Variadic::Type::kUint:
+        // BEWARE: uint64 is handled as signed int64 for SQLite operations.
+        return sqlite_utils::CompareValuesAsc(
+            static_cast<int64_t>(arg_f.uint_value),
+            static_cast<int64_t>(arg_s.uint_value));
       case Variadic::Type::kString: {
         const auto& f_str = storage_->GetString(arg_f.string_value);
         const auto& s_str = storage_->GetString(arg_s.string_value);
         return sqlite_utils::CompareValuesAsc(f_str, s_str);
       }
+      case Variadic::Type::kReal:
+        return sqlite_utils::CompareValuesAsc(arg_f.real_value,
+                                              arg_s.real_value);
+      case Variadic::Type::kPointer:
+        // BEWARE: pointers are handled as signed int64 for SQLite operations.
+        return sqlite_utils::CompareValuesAsc(
+            static_cast<int64_t>(arg_f.pointer_value),
+            static_cast<int64_t>(arg_s.pointer_value));
+      case Variadic::Type::kBool:
+        return sqlite_utils::CompareValuesAsc(arg_f.bool_value,
+                                              arg_s.bool_value);
+      case Variadic::Type::kJson: {
+        const auto& f_str = storage_->GetString(arg_f.json_value);
+        const auto& s_str = storage_->GetString(arg_s.json_value);
+        return sqlite_utils::CompareValuesAsc(f_str, s_str);
+      }
     }
   } else if (arg_s.type == type_) {
     return -1;
diff --git a/src/trace_processor/args_table.h b/src/trace_processor/args_table.h
index 0d9fd4b..3327e57 100644
--- a/src/trace_processor/args_table.h
+++ b/src/trace_processor/args_table.h
@@ -56,9 +56,17 @@
       switch (type_) {
         case Variadic::Type::kInt:
           return Table::ColumnType::kLong;
+        case Variadic::Type::kUint:
+          return Table::ColumnType::kLong;
+        case Variadic::Type::kString:
+          return Table::ColumnType::kString;
         case Variadic::Type::kReal:
           return Table::ColumnType::kDouble;
-        case Variadic::Type::kString:
+        case Variadic::Type::kPointer:
+          return Table::ColumnType::kLong;
+        case Variadic::Type::kBool:
+          return Table::ColumnType::kBool;
+        case Variadic::Type::kJson:
           return Table::ColumnType::kString;
       }
       PERFETTO_FATAL("Not reached");  // For gcc
diff --git a/src/trace_processor/args_tracker.h b/src/trace_processor/args_tracker.h
index b966dc9..7eb27d7 100644
--- a/src/trace_processor/args_tracker.h
+++ b/src/trace_processor/args_tracker.h
@@ -32,10 +32,12 @@
   virtual ~ArgsTracker();
 
   // Adds a arg for this row id with the given key and value.
-  void AddArg(RowId row_id, StringId flat_key, StringId key, Variadic);
+  // Virtual for testing.
+  virtual void AddArg(RowId row_id, StringId flat_key, StringId key, Variadic);
 
   // Commits the added args to storage.
-  void Flush();
+  // Virtual for testing.
+  virtual void Flush();
 
  private:
   std::vector<TraceStorage::Args::Arg> args_;
diff --git a/src/trace_processor/proto_incremental_state.h b/src/trace_processor/proto_incremental_state.h
index 38f4abd..b2dc1de 100644
--- a/src/trace_processor/proto_incremental_state.h
+++ b/src/trace_processor/proto_incremental_state.h
@@ -77,7 +77,7 @@
   struct InternedDataView {
     InternedDataView(TraceBlobView msg) : message(std::move(msg)) {}
 
-    typename MessageType::Decoder CreateDecoder() {
+    typename MessageType::Decoder CreateDecoder() const {
       return typename MessageType::Decoder(message.data(), message.length());
     }
 
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index ae94745..3355766 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -118,7 +118,11 @@
       oom_score_adj_id_(context->storage->InternString("oom_score_adj")),
       ion_total_unknown_id_(context->storage->InternString("mem.ion.unknown")),
       ion_change_unknown_id_(
-          context->storage->InternString("mem.ion_change.unknown")) {
+          context->storage->InternString("mem.ion_change.unknown")),
+      task_file_name_args_key_id_(
+          context->storage->InternString("task.posted_from.file_name")),
+      task_function_name_args_key_id_(
+          context->storage->InternString("task.posted_from.function_name")) {
   for (const auto& name : BuildMeminfoCounterNames()) {
     meminfo_strs_id_.emplace_back(context->storage->InternString(name));
   }
@@ -1436,21 +1440,35 @@
   // TODO(eseckler): Handle thread timestamp/duration, debug annotations, task
   // souce locations, legacy event attributes, ...
 
+  auto args_callback = [this, &event, &sequence_state](
+                           ArgsTracker* args_tracker, RowId row) {
+    for (auto it = event.debug_annotations(); it; ++it) {
+      ParseDebugAnnotationArgs(it->as_bytes(), sequence_state, args_tracker,
+                               row);
+    }
+
+    if (event.has_task_execution()) {
+      ParseTaskExecutionArgs(event.task_execution(), sequence_state,
+                             args_tracker, row);
+    }
+  };
+
   int32_t phase = legacy_event.phase();
   switch (static_cast<char>(phase)) {
     case 'B': {  // TRACE_EVENT_PHASE_BEGIN.
-      slice_tracker->Begin(ts, utid, category_id, name_id);
+      slice_tracker->Begin(ts, utid, category_id, name_id, args_callback);
       break;
     }
     case 'E': {  // TRACE_EVENT_PHASE_END.
-      slice_tracker->End(ts, utid, category_id, name_id);
+      slice_tracker->End(ts, utid, category_id, name_id, args_callback);
       break;
     }
     case 'X': {  // TRACE_EVENT_PHASE_COMPLETE.
       auto duration_ns = legacy_event.duration_us() * 1000;
       if (duration_ns < 0)
         return;
-      slice_tracker->Scoped(ts, utid, category_id, name_id, duration_ns);
+      slice_tracker->Scoped(ts, utid, category_id, name_id, duration_ns,
+                            args_callback);
       break;
     }
     case 'M': {  // TRACE_EVENT_PHASE_METADATA (process and thread names).
@@ -1486,6 +1504,176 @@
   }
 }
 
+void ProtoTraceParser::ParseDebugAnnotationArgs(
+    ConstBytes debug_annotation,
+    ProtoIncrementalState::PacketSequenceState* sequence_state,
+    ArgsTracker* args_tracker,
+    RowId row) {
+  protos::pbzero::DebugAnnotation::Decoder annotation(debug_annotation.data,
+                                                      debug_annotation.size);
+  uint32_t iid = annotation.name_iid();
+  if (!iid)
+    return;
+
+  auto* map =
+      sequence_state->GetInternedDataMap<protos::pbzero::DebugAnnotationName>();
+  auto name_view_it = map->find(iid);
+  if (name_view_it == map->end()) {
+    PERFETTO_ELOG(
+        "Could not find debug annotation name interning entry for ID %u", iid);
+    return;
+  }
+
+  TraceStorage* storage = context_->storage.get();
+
+  StringId name_id = 0;
+
+  // If the name is already in the pool, no need to decode it again.
+  if (name_view_it->second.storage_refs) {
+    name_id = name_view_it->second.storage_refs->name_id;
+  } else {
+    auto name = name_view_it->second.CreateDecoder();
+    std::string name_prefixed = "debug." + name.name().ToStdString();
+    name_id = storage->InternString(base::StringView(name_prefixed));
+    // Avoid having to decode & look up the name again in the future.
+    name_view_it->second.storage_refs =
+        ProtoIncrementalState::StorageReferences<
+            protos::pbzero::DebugAnnotationName>{name_id};
+  }
+
+  if (annotation.has_bool_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::Boolean(annotation.bool_value()));
+  } else if (annotation.has_uint_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::UnsignedInteger(annotation.uint_value()));
+  } else if (annotation.has_int_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::Integer(annotation.int_value()));
+  } else if (annotation.has_double_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::Real(annotation.double_value()));
+  } else if (annotation.has_string_value()) {
+    args_tracker->AddArg(
+        row, name_id, name_id,
+        Variadic::String(storage->InternString(annotation.string_value())));
+  } else if (annotation.has_pointer_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::Pointer(annotation.pointer_value()));
+  } else if (annotation.has_legacy_json_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::String(storage->InternString(
+                             annotation.legacy_json_value())));
+  } else if (annotation.has_nested_value()) {
+    auto name = storage->GetString(name_id);
+    ParseNestedValueArgs(annotation.nested_value(), name, name, args_tracker,
+                         row);
+  }
+}
+
+void ProtoTraceParser::ParseNestedValueArgs(ConstBytes nested_value,
+                                            base::StringView flat_key,
+                                            base::StringView key,
+                                            ArgsTracker* args_tracker,
+                                            RowId row) {
+  protos::pbzero::DebugAnnotation::NestedValue::Decoder value(
+      nested_value.data, nested_value.size);
+  switch (value.nested_type()) {
+    case protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED: {
+      auto flat_key_id = context_->storage->InternString(flat_key);
+      auto key_id = context_->storage->InternString(key);
+      // Leaf value.
+      if (value.has_bool_value()) {
+        args_tracker->AddArg(row, flat_key_id, key_id,
+                             Variadic::Boolean(value.bool_value()));
+      } else if (value.has_int_value()) {
+        args_tracker->AddArg(row, flat_key_id, key_id,
+                             Variadic::Integer(value.int_value()));
+      } else if (value.has_double_value()) {
+        args_tracker->AddArg(row, flat_key_id, key_id,
+                             Variadic::Real(value.double_value()));
+      } else if (value.has_string_value()) {
+        args_tracker->AddArg(row, flat_key_id, key_id,
+                             Variadic::String(context_->storage->InternString(
+                                 value.string_value())));
+      }
+      break;
+    }
+    case protos::pbzero::DebugAnnotation::NestedValue::DICT: {
+      auto key_it = value.dict_keys();
+      auto value_it = value.dict_values();
+      for (; key_it && value_it; ++key_it, ++value_it) {
+        std::string child_name = key_it->as_std_string();
+        std::string child_flat_key = flat_key.ToStdString() + "." + child_name;
+        std::string child_key = key.ToStdString() + "." + child_name;
+        ParseNestedValueArgs(value_it->as_bytes(),
+                             base::StringView(child_flat_key),
+                             base::StringView(child_key), args_tracker, row);
+      }
+      break;
+    }
+    case protos::pbzero::DebugAnnotation::NestedValue::ARRAY: {
+      int child_index = 0;
+      std::string child_flat_key = flat_key.ToStdString();
+      for (auto value_it = value.array_values(); value_it;
+           ++value_it, ++child_index) {
+        std::string child_key =
+            key.ToStdString() + "[" + std::to_string(child_index) + "]";
+        ParseNestedValueArgs(value_it->as_bytes(),
+                             base::StringView(child_flat_key),
+                             base::StringView(child_key), args_tracker, row);
+      }
+      break;
+    }
+  }
+}
+
+void ProtoTraceParser::ParseTaskExecutionArgs(
+    ConstBytes task_execution,
+    ProtoIncrementalState::PacketSequenceState* sequence_state,
+    ArgsTracker* args_tracker,
+    RowId row) {
+  protos::pbzero::TaskExecution::Decoder task(task_execution.data,
+                                              task_execution.size);
+  uint32_t iid = task.posted_from_iid();
+  if (!iid)
+    return;
+
+  auto* map =
+      sequence_state->GetInternedDataMap<protos::pbzero::SourceLocation>();
+  auto location_view_it = map->find(iid);
+  if (location_view_it == map->end()) {
+    PERFETTO_ELOG("Could not find source location interning entry for ID %u",
+                  iid);
+    return;
+  }
+
+  StringId file_name_id = 0;
+  StringId function_name_id = 0;
+
+  // If the names are already in the pool, no need to decode them again.
+  if (location_view_it->second.storage_refs) {
+    file_name_id = location_view_it->second.storage_refs->file_name_id;
+    function_name_id = location_view_it->second.storage_refs->function_name_id;
+  } else {
+    TraceStorage* storage = context_->storage.get();
+    auto location = location_view_it->second.CreateDecoder();
+    file_name_id = storage->InternString(location.file_name());
+    function_name_id = storage->InternString(location.function_name());
+    // Avoid having to decode & look up the names again in the future.
+    location_view_it->second.storage_refs =
+        ProtoIncrementalState::StorageReferences<
+            protos::pbzero::SourceLocation>{file_name_id, function_name_id};
+  }
+
+  args_tracker->AddArg(row, task_file_name_args_key_id_,
+                       task_file_name_args_key_id_,
+                       Variadic::String(file_name_id));
+  args_tracker->AddArg(row, task_function_name_args_key_id_,
+                       task_function_name_args_key_id_,
+                       Variadic::String(function_name_id));
+}
+
 void ProtoTraceParser::ParseChromeBenchmarkMetadata(ConstBytes blob) {
   TraceStorage* storage = context_->storage.get();
   protos::pbzero::ChromeBenchmarkMetadata::Decoder packet(blob.data, blob.size);
diff --git a/src/trace_processor/proto_trace_parser.h b/src/trace_processor/proto_trace_parser.h
index a66573d..0fbe329 100644
--- a/src/trace_processor/proto_trace_parser.h
+++ b/src/trace_processor/proto_trace_parser.h
@@ -33,6 +33,7 @@
 namespace perfetto {
 namespace trace_processor {
 
+class ArgsTracker;
 class TraceProcessorContext;
 
 class ProtoTraceParser : public TraceParser {
@@ -92,6 +93,21 @@
                        int64_t tts,
                        ProtoIncrementalState::PacketSequenceState*,
                        ConstBytes);
+  void ParseDebugAnnotationArgs(
+      ConstBytes debug_annotation,
+      ProtoIncrementalState::PacketSequenceState* sequence_state,
+      ArgsTracker* args_tracker,
+      RowId row);
+  void ParseNestedValueArgs(ConstBytes nested_value,
+                            base::StringView flat_key,
+                            base::StringView key,
+                            ArgsTracker* args_tracker,
+                            RowId row);
+  void ParseTaskExecutionArgs(
+      ConstBytes task_execution,
+      ProtoIncrementalState::PacketSequenceState* sequence_state,
+      ArgsTracker* args_tracker,
+      RowId row);
   void ParseChromeBenchmarkMetadata(ConstBytes);
 
  private:
@@ -123,6 +139,8 @@
   const StringId oom_score_adj_id_;
   const StringId ion_total_unknown_id_;
   const StringId ion_change_unknown_id_;
+  const StringId task_file_name_args_key_id_;
+  const StringId task_function_name_args_key_id_;
   std::vector<StringId> meminfo_strs_id_;
   std::vector<StringId> vmstat_strs_id_;
   std::vector<StringId> rss_members_;
diff --git a/src/trace_processor/proto_trace_parser_unittest.cc b/src/trace_processor/proto_trace_parser_unittest.cc
index 6107f4f..a40868d 100644
--- a/src/trace_processor/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/proto_trace_parser_unittest.cc
@@ -104,6 +104,7 @@
   MockTraceStorage() : TraceStorage() {}
 
   MOCK_METHOD1(InternString, StringId(base::StringView));
+  MOCK_CONST_METHOD1(GetString, NullTermStringView(StringId));
   MOCK_METHOD2(SetMetadata, void(size_t, Variadic));
   MOCK_METHOD2(AppendMetadata, void(size_t, Variadic));
 };
@@ -147,8 +148,7 @@
   ProtoTraceParserTest() {
     nice_storage_ = new NiceMock<MockTraceStorage>();
     context_.storage.reset(nice_storage_);
-    args_ = new MockArgsTracker(&context_);
-    context_.args_tracker.reset(args_);
+    context_.args_tracker.reset(new ArgsTracker(&context_));
     event_ = new MockEventTracker(&context_);
     context_.event_tracker.reset(event_);
     process_ = new MockProcessTracker(&context_);
@@ -189,7 +189,6 @@
   std::unique_ptr<protozero::ScatteredStreamWriter> stream_writer_;
   protos::pbzero::Trace trace_;
   TraceProcessorContext context_;
-  MockArgsTracker* args_;
   MockEventTracker* event_;
   MockProcessTracker* process_;
   MockSliceTracker* slice_;
@@ -976,6 +975,264 @@
   context_.sorter->ExtractEventsForced();
 }
 
+TEST_F(ProtoTraceParserTest, TrackEventWithDebugAnnotations) {
+  InitStorage();
+  context_.sorter.reset(new TraceSorter(
+      &context_, std::numeric_limits<int64_t>::max() /*window size*/));
+  MockArgsTracker args(&context_);
+
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    packet->set_incremental_state_cleared(true);
+    auto* thread_desc = packet->set_thread_descriptor();
+    thread_desc->set_pid(15);
+    thread_desc->set_tid(16);
+    thread_desc->set_reference_timestamp_us(1000);
+    thread_desc->set_reference_thread_time_us(2000);
+  }
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    auto* event = packet->set_track_event();
+    event->set_timestamp_delta_us(10);   // absolute: 1010.
+    event->set_thread_time_delta_us(5);  // absolute: 2005.
+    event->add_category_iids(1);
+    auto* annotation1 = event->add_debug_annotations();
+    annotation1->set_name_iid(1);
+    annotation1->set_uint_value(10u);
+    auto* annotation2 = event->add_debug_annotations();
+    annotation2->set_name_iid(2);
+    auto* nested = annotation2->set_nested_value();
+    nested->set_nested_type(protos::pbzero::DebugAnnotation::NestedValue::DICT);
+    nested->add_dict_keys("child1");
+    nested->add_dict_keys("child2");
+    auto* child1 = nested->add_dict_values();
+    child1->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+    child1->set_bool_value(true);
+    auto* child2 = nested->add_dict_values();
+    child2->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::ARRAY);
+    auto* child21 = child2->add_array_values();
+    child21->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+    child21->set_string_value("child21");
+    auto* child22 = child2->add_array_values();
+    child22->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+    child22->set_double_value(2.2);
+    auto* child23 = child2->add_array_values();
+    child23->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+    child23->set_int_value(23);
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_name_iid(1);
+    legacy_event->set_phase('B');
+
+    auto* interned_data = packet->set_interned_data();
+    auto cat1 = interned_data->add_event_categories();
+    cat1->set_iid(1);
+    cat1->set_name("cat1");
+    auto ev1 = interned_data->add_legacy_event_names();
+    ev1->set_iid(1);
+    ev1->set_name("ev1");
+    auto an1 = interned_data->add_debug_annotation_names();
+    an1->set_iid(1);
+    an1->set_name("an1");
+    auto an2 = interned_data->add_debug_annotation_names();
+    an2->set_iid(2);
+    an2->set_name("an2");
+  }
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    auto* event = packet->set_track_event();
+    event->set_timestamp_delta_us(10);   // absolute: 1020.
+    event->set_thread_time_delta_us(5);  // absolute: 2010.
+    event->add_category_iids(1);
+    auto* annotation3 = event->add_debug_annotations();
+    annotation3->set_name_iid(3);
+    annotation3->set_int_value(-3);
+    auto* annotation4 = event->add_debug_annotations();
+    annotation4->set_name_iid(4);
+    annotation4->set_bool_value(true);
+    auto* annotation5 = event->add_debug_annotations();
+    annotation5->set_name_iid(5);
+    annotation5->set_double_value(-5.5);
+    auto* annotation6 = event->add_debug_annotations();
+    annotation6->set_name_iid(6);
+    annotation6->set_pointer_value(20u);
+    auto* annotation7 = event->add_debug_annotations();
+    annotation7->set_name_iid(7);
+    annotation7->set_string_value("val7");
+    auto* annotation8 = event->add_debug_annotations();
+    annotation8->set_name_iid(8);
+    annotation8->set_legacy_json_value("val8");
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_name_iid(1);
+    legacy_event->set_phase('E');
+
+    auto* interned_data = packet->set_interned_data();
+    auto an3 = interned_data->add_debug_annotation_names();
+    an3->set_iid(3);
+    an3->set_name("an3");
+    auto an4 = interned_data->add_debug_annotation_names();
+    an4->set_iid(4);
+    an4->set_name("an4");
+    auto an5 = interned_data->add_debug_annotation_names();
+    an5->set_iid(5);
+    an5->set_name("an5");
+    auto an6 = interned_data->add_debug_annotation_names();
+    an6->set_iid(6);
+    an6->set_name("an6");
+    auto an7 = interned_data->add_debug_annotation_names();
+    an7->set_iid(7);
+    an7->set_name("an7");
+    auto an8 = interned_data->add_debug_annotation_names();
+    an8->set_iid(8);
+    an8->set_name("an8");
+  }
+
+  Tokenize();
+
+  EXPECT_CALL(*process_, UpdateThread(16, 15))
+      .Times(2)
+      .WillRepeatedly(Return(1));
+
+  InSequence in_sequence;  // Below slices should be sorted by timestamp.
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
+      .WillOnce(Return(1));
+  EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
+      .WillOnce(Return(2));
+  EXPECT_CALL(*slice_, Begin(1010000, 1, 1, 2, _))
+      .WillOnce(testing::InvokeArgument<4>(&args, 1u));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an1")))
+      .WillOnce(Return(3));
+  EXPECT_CALL(args, AddArg(1u, 3, 3, Variadic::UnsignedInteger(10u)));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2")))
+      .WillOnce(Return(4));
+  EXPECT_CALL(*storage_, GetString(4)).WillOnce(Return("debug.an2"));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child1")))
+      .Times(2)
+      .WillRepeatedly(Return(5));
+  EXPECT_CALL(args, AddArg(1u, 5, 5, Variadic::Boolean(true)));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2")))
+      .WillOnce(Return(6));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2[0]")))
+      .WillOnce(Return(7));
+  EXPECT_CALL(*storage_, InternString(base::StringView("child21")))
+      .WillOnce(Return(8));
+  EXPECT_CALL(args, AddArg(1u, 6, 7, Variadic::String(8)));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2")))
+      .WillOnce(Return(6));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2[1]")))
+      .WillOnce(Return(9));
+  EXPECT_CALL(args, AddArg(1u, 6, 9, Variadic::Real(2.2)));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2")))
+      .WillOnce(Return(6));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2[2]")))
+      .WillOnce(Return(10));
+  EXPECT_CALL(args, AddArg(1u, 6, 10, Variadic::Integer(23)));
+
+  EXPECT_CALL(*slice_, End(1020000, 1, 1, 2, _))
+      .WillOnce(testing::InvokeArgument<4>(&args, 1u));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an3")))
+      .WillOnce(Return(11));
+  EXPECT_CALL(args, AddArg(1u, 11, 11, Variadic::Integer(-3)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an4")))
+      .WillOnce(Return(12));
+  EXPECT_CALL(args, AddArg(1u, 12, 12, Variadic::Boolean(true)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an5")))
+      .WillOnce(Return(13));
+  EXPECT_CALL(args, AddArg(1u, 13, 13, Variadic::Real(-5.5)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an6")))
+      .WillOnce(Return(14));
+  EXPECT_CALL(args, AddArg(1u, 14, 14, Variadic::Pointer(20u)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an7")))
+      .WillOnce(Return(15));
+  EXPECT_CALL(*storage_, InternString(base::StringView("val7")))
+      .WillOnce(Return(16));
+  EXPECT_CALL(args, AddArg(1u, 15, 15, Variadic::String(16)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an8")))
+      .WillOnce(Return(17));
+  EXPECT_CALL(*storage_, InternString(base::StringView("val8")))
+      .WillOnce(Return(18));
+  EXPECT_CALL(args, AddArg(1u, 17, 17, Variadic::String(18)));
+
+  context_.sorter->ExtractEventsForced();
+}
+
+TEST_F(ProtoTraceParserTest, TrackEventWithTaskExecution) {
+  InitStorage();
+  context_.sorter.reset(new TraceSorter(
+      &context_, std::numeric_limits<int64_t>::max() /*window size*/));
+  MockArgsTracker args(&context_);
+
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    packet->set_incremental_state_cleared(true);
+    auto* thread_desc = packet->set_thread_descriptor();
+    thread_desc->set_pid(15);
+    thread_desc->set_tid(16);
+    thread_desc->set_reference_timestamp_us(1000);
+    thread_desc->set_reference_thread_time_us(2000);
+  }
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    auto* event = packet->set_track_event();
+    event->set_timestamp_delta_us(10);   // absolute: 1010.
+    event->set_thread_time_delta_us(5);  // absolute: 2005.
+    event->add_category_iids(1);
+    auto* task_execution = event->set_task_execution();
+    task_execution->set_posted_from_iid(1);
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_name_iid(1);
+    legacy_event->set_phase('B');
+
+    auto* interned_data = packet->set_interned_data();
+    auto cat1 = interned_data->add_event_categories();
+    cat1->set_iid(1);
+    cat1->set_name("cat1");
+    auto ev1 = interned_data->add_legacy_event_names();
+    ev1->set_iid(1);
+    ev1->set_name("ev1");
+    auto loc1 = interned_data->add_source_locations();
+    loc1->set_iid(1);
+    loc1->set_file_name("file1");
+    loc1->set_function_name("func1");
+  }
+
+  Tokenize();
+
+  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+
+  InSequence in_sequence;  // Below slices should be sorted by timestamp.
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
+      .WillOnce(Return(1));
+  EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
+      .WillOnce(Return(2));
+  EXPECT_CALL(*slice_, Begin(1010000, 1, 1, 2, _))
+      .WillOnce(testing::InvokeArgument<4>(&args, 1u));
+  EXPECT_CALL(*storage_, InternString(base::StringView("file1")))
+      .WillOnce(Return(3));
+  EXPECT_CALL(*storage_, InternString(base::StringView("func1")))
+      .WillOnce(Return(4));
+  EXPECT_CALL(args, AddArg(1u, _, _, Variadic::String(3)));
+  EXPECT_CALL(args, AddArg(1u, _, _, Variadic::String(4)));
+
+  context_.sorter->ExtractEventsForced();
+}
+
 TEST_F(ProtoTraceParserTest, LoadChromeBenchmarkMetadata) {
   static const char kName[] = "name";
   static const char kTag2[] = "tag1";
diff --git a/src/trace_processor/raw_table.cc b/src/trace_processor/raw_table.cc
index 1f9196e..d7dcffe 100644
--- a/src/trace_processor/raw_table.cc
+++ b/src/trace_processor/raw_table.cc
@@ -90,12 +90,27 @@
       case Variadic::kInt:
         writer->AppendInt(value.int_value);
         break;
-      case Variadic::kReal:
-        writer->AppendDouble(value.real_value);
+      case Variadic::kUint:
+        writer->AppendUnsignedInt(value.uint_value);
         break;
       case Variadic::kString: {
         const auto& str = storage_->GetString(value.string_value);
         writer->AppendString(str.c_str(), str.size());
+        break;
+      }
+      case Variadic::kReal:
+        writer->AppendDouble(value.real_value);
+        break;
+      case Variadic::kPointer:
+        writer->AppendUnsignedInt(value.pointer_value);
+        break;
+      case Variadic::kBool:
+        writer->AppendBool(value.bool_value);
+        break;
+      case Variadic::kJson: {
+        const auto& str = storage_->GetString(value.json_value);
+        writer->AppendString(str.c_str(), str.size());
+        break;
       }
     }
   };
diff --git a/src/trace_processor/sqlite_utils.h b/src/trace_processor/sqlite_utils.h
index 60d095f..64ad4fb 100644
--- a/src/trace_processor/sqlite_utils.h
+++ b/src/trace_processor/sqlite_utils.h
@@ -132,6 +132,13 @@
   return sqlite3_value_double(value);
 }
 
+template <>
+inline bool ExtractSqliteValue(sqlite3_value* value) {
+  auto type = sqlite3_value_type(value);
+  PERFETTO_DCHECK(type == SQLITE_INTEGER);
+  return static_cast<bool>(sqlite3_value_int(value));
+}
+
 // Do not add a uint64_t version of ExtractSqliteValue. You should not be using
 // uint64_t at all given that SQLite doesn't support it.
 
@@ -362,6 +369,11 @@
 }
 
 template <>
+inline void ReportSqliteResult(sqlite3_context* ctx, bool value) {
+  sqlite3_result_int(ctx, value);
+}
+
+template <>
 inline void ReportSqliteResult(sqlite3_context* ctx, double value) {
   sqlite3_result_double(ctx, value);
 }
@@ -430,6 +442,8 @@
       type = Table::ColumnType::kString;
     } else if (strcmp(raw_type, "DOUBLE") == 0) {
       type = Table::ColumnType::kDouble;
+    } else if (strcmp(raw_type, "BOOLEAN") == 0) {
+      type = Table::ColumnType::kBool;
     } else if (!*raw_type) {
       PERFETTO_DLOG("Unknown column type for %s %s", raw_table_name.c_str(),
                     name);
diff --git a/src/trace_processor/table.cc b/src/trace_processor/table.cc
index 1218b5a..1110d6f 100644
--- a/src/trace_processor/table.cc
+++ b/src/trace_processor/table.cc
@@ -39,6 +39,8 @@
       return "INT";
     case Table::ColumnType::kDouble:
       return "DOUBLE";
+    case Table::ColumnType::kBool:
+      return "BOOLEAN";
     case Table::ColumnType::kUnknown:
       PERFETTO_FATAL("Cannot map unknown column type");
   }
diff --git a/src/trace_processor/table.h b/src/trace_processor/table.h
index 0c8e638..c89f002 100644
--- a/src/trace_processor/table.h
+++ b/src/trace_processor/table.h
@@ -49,7 +49,8 @@
     kLong = 3,
     kInt = 4,
     kDouble = 5,
-    kUnknown = 6,
+    kBool = 6,
+    kUnknown = 7,
   };
 
   // Describes a column of this table.
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 45ffed3..de4dfa5 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -131,12 +131,24 @@
           case Variadic::Type::kInt:
             hash.Update(arg.value.int_value);
             break;
+          case Variadic::Type::kUint:
+            hash.Update(arg.value.uint_value);
+            break;
           case Variadic::Type::kString:
             hash.Update(arg.value.string_value);
             break;
           case Variadic::Type::kReal:
             hash.Update(arg.value.real_value);
             break;
+          case Variadic::Type::kPointer:
+            hash.Update(arg.value.pointer_value);
+            break;
+          case Variadic::Type::kBool:
+            hash.Update(arg.value.bool_value);
+            break;
+          case Variadic::Type::kJson:
+            hash.Update(arg.value.json_value);
+            break;
         }
         return hash.digest();
       }
@@ -832,7 +844,8 @@
   }
 
   // Reading methods.
-  NullTermStringView GetString(StringId id) const {
+  // Virtual for testing.
+  virtual NullTermStringView GetString(StringId id) const {
     return string_pool_.Get(id);
   }
 
diff --git a/src/trace_processor/variadic.h b/src/trace_processor/variadic.h
index 761aea3..6df011e 100644
--- a/src/trace_processor/variadic.h
+++ b/src/trace_processor/variadic.h
@@ -24,7 +24,7 @@
 
 // Variadic type representing value of different possible types.
 struct Variadic {
-  enum Type { kInt, kString, kReal };
+  enum Type { kInt, kUint, kString, kReal, kPointer, kBool, kJson };
 
   static Variadic Integer(int64_t int_value) {
     Variadic variadic;
@@ -33,6 +33,17 @@
     return variadic;
   }
 
+  // BEWARE: Unsigned 64-bit integers will be handled as signed integers by
+  // SQLite for built-in SQL operators. This variadic type is used to
+  // distinguish between int64 and uint64 for correct JSON export of TrackEvent
+  // arguments.
+  static Variadic UnsignedInteger(uint64_t uint_value) {
+    Variadic variadic;
+    variadic.type = Type::kUint;
+    variadic.uint_value = uint_value;
+    return variadic;
+  }
+
   static Variadic String(StringPool::Id string_id) {
     Variadic variadic;
     variadic.type = Type::kString;
@@ -47,16 +58,49 @@
     return variadic;
   }
 
+  // This variadic type is used to distinguish between integers and pointer
+  // values for correct JSON export of TrackEvent arguments.
+  static Variadic Pointer(uint64_t pointer_value) {
+    Variadic variadic;
+    variadic.type = Type::kPointer;
+    variadic.pointer_value = pointer_value;
+    return variadic;
+  }
+
+  static Variadic Boolean(bool bool_value) {
+    Variadic variadic;
+    variadic.type = Type::kBool;
+    variadic.bool_value = bool_value;
+    return variadic;
+  }
+
+  // This variadic type is used to distinguish between regular string and JSON
+  // string values for correct JSON export of TrackEvent arguments.
+  static Variadic Json(StringPool::Id json_value) {
+    Variadic variadic;
+    variadic.type = Type::kJson;
+    variadic.json_value = json_value;
+    return variadic;
+  }
+
   // Used in tests.
   bool operator==(const Variadic& other) const {
     if (type == other.type) {
       switch (type) {
         case kInt:
           return int_value == other.int_value;
+        case kUint:
+          return uint_value == other.uint_value;
         case kString:
           return string_value == other.string_value;
         case kReal:
           return std::equal_to<double>()(real_value, other.real_value);
+        case kPointer:
+          return pointer_value == other.pointer_value;
+        case kBool:
+          return bool_value == other.bool_value;
+        case kJson:
+          return json_value == other.json_value;
       }
     }
     return false;
@@ -65,8 +109,12 @@
   Type type;
   union {
     int64_t int_value;
+    uint64_t uint_value;
     StringPool::Id string_value;
     double real_value;
+    uint64_t pointer_value;
+    bool bool_value;
+    StringPool::Id json_value;
   };
 };