Merge "shared_lib: Support extensions in the protoc macros" into main
diff --git a/include/perfetto/public/pb_macros.h b/include/perfetto/public/pb_macros.h
index 03724a5..ba12ed2 100644
--- a/include/perfetto/public/pb_macros.h
+++ b/include/perfetto/public/pb_macros.h
@@ -110,87 +110,87 @@
 
 #define PERFETTO_I_PB_GET_MSG(C_TYPE) PERFETTO_I_PB_CONCAT_3(C_TYPE, _, get_msg)
 
-#define PERFETTO_I_PB_FIELD_STRING(PROTO, C_TYPE, NAME, NUM)              \
-  static inline void PERFETTO_I_PB_SETTER_CSTR_NAME(PROTO, NAME)(         \
+#define PERFETTO_I_PB_FIELD_STRING(PREFIX, PROTO, C_TYPE, NAME, NUM)      \
+  static inline void PERFETTO_I_PB_SETTER_CSTR_NAME(PREFIX, NAME)(        \
       struct PROTO * msg, const char* value) {                            \
     PerfettoPbMsgAppendCStrField(&msg->msg, NUM, value);                  \
   }                                                                       \
-  static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)(              \
+  static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)(             \
       struct PROTO * msg, const void* data, size_t len) {                 \
     PerfettoPbMsgAppendType2Field(                                        \
         &msg->msg, NUM, PERFETTO_STATIC_CAST(const uint8_t*, data), len); \
   }                                                                       \
-  static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PROTO, NAME)(        \
+  static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PREFIX, NAME)(       \
       struct PROTO * msg, struct PerfettoPbMsg * nested) {                \
     PerfettoPbMsgBeginNested(&msg->msg, nested, NUM);                     \
   }                                                                       \
-  static inline void PERFETTO_I_PB_SETTER_END_NAME(PROTO, NAME)(          \
+  static inline void PERFETTO_I_PB_SETTER_END_NAME(PREFIX, NAME)(         \
       struct PROTO * msg, struct PerfettoPbMsg * nested) {                \
     (void)nested;                                                         \
     PerfettoPbMsgEndNested(&msg->msg);                                    \
   }
 
-#define PERFETTO_I_PB_FIELD_VARINT(PROTO, C_TYPE, NAME, NUM)              \
-  static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)(              \
+#define PERFETTO_I_PB_FIELD_VARINT(PREFIX, PROTO, C_TYPE, NAME, NUM)      \
+  static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)(             \
       struct PROTO * msg, C_TYPE value) {                                 \
     PerfettoPbMsgAppendType0Field(&msg->msg, NUM,                         \
                                   PERFETTO_STATIC_CAST(uint64_t, value)); \
   }
 
-#define PERFETTO_I_PB_FIELD_ZIGZAG(PROTO, C_TYPE, NAME, NUM)            \
-  static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)(            \
+#define PERFETTO_I_PB_FIELD_ZIGZAG(PREFIX, PROTO, C_TYPE, NAME, NUM)    \
+  static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)(           \
       struct PROTO * msg, C_TYPE value) {                               \
     uint64_t encoded =                                                  \
         PerfettoPbZigZagEncode64(PERFETTO_STATIC_CAST(int64_t, value)); \
     PerfettoPbMsgAppendType0Field(&msg->msg, NUM, encoded);             \
   }
 
-#define PERFETTO_I_PB_FIELD_FIXED64(PROTO, C_TYPE, NAME, NUM) \
-  static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)(  \
-      struct PROTO * msg, C_TYPE value) {                     \
-    uint64_t val;                                             \
-    memcpy(&val, &value, sizeof val);                         \
-    PerfettoPbMsgAppendFixed64Field(&msg->msg, NUM, val);     \
+#define PERFETTO_I_PB_FIELD_FIXED64(PREFIX, PROTO, C_TYPE, NAME, NUM) \
+  static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)(         \
+      struct PROTO * msg, C_TYPE value) {                             \
+    uint64_t val;                                                     \
+    memcpy(&val, &value, sizeof val);                                 \
+    PerfettoPbMsgAppendFixed64Field(&msg->msg, NUM, val);             \
   }
 
-#define PERFETTO_I_PB_FIELD_FIXED32(PROTO, C_TYPE, NAME, NUM) \
-  static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)(  \
-      struct PROTO * msg, C_TYPE value) {                     \
-    uint32_t val;                                             \
-    memcpy(&val, &value, sizeof val);                         \
-    PerfettoPbMsgAppendFixed32Field(&msg->msg, NUM, val);     \
+#define PERFETTO_I_PB_FIELD_FIXED32(PREFIX, PROTO, C_TYPE, NAME, NUM) \
+  static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)(         \
+      struct PROTO * msg, C_TYPE value) {                             \
+    uint32_t val;                                                     \
+    memcpy(&val, &value, sizeof val);                                 \
+    PerfettoPbMsgAppendFixed32Field(&msg->msg, NUM, val);             \
   }
 
-#define PERFETTO_I_PB_FIELD_MSG(PROTO, C_TYPE, NAME, NUM)          \
-  static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PROTO, NAME)( \
-      struct PROTO * msg, struct C_TYPE * nested) {                \
-    struct PerfettoPbMsg* nested_msg =                             \
-        PERFETTO_REINTERPRET_CAST(struct PerfettoPbMsg*, nested);  \
-    PerfettoPbMsgBeginNested(&msg->msg, nested_msg, NUM);          \
-  }                                                                \
-  static inline void PERFETTO_I_PB_SETTER_END_NAME(PROTO, NAME)(   \
-      struct PROTO * msg, struct C_TYPE * nested) {                \
-    (void)nested;                                                  \
-    PerfettoPbMsgEndNested(&msg->msg);                             \
+#define PERFETTO_I_PB_FIELD_MSG(PREFIX, PROTO, C_TYPE, NAME, NUM)   \
+  static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PREFIX, NAME)( \
+      struct PROTO * msg, struct C_TYPE * nested) {                 \
+    struct PerfettoPbMsg* nested_msg =                              \
+        PERFETTO_REINTERPRET_CAST(struct PerfettoPbMsg*, nested);   \
+    PerfettoPbMsgBeginNested(&msg->msg, nested_msg, NUM);           \
+  }                                                                 \
+  static inline void PERFETTO_I_PB_SETTER_END_NAME(PREFIX, NAME)(   \
+      struct PROTO * msg, struct C_TYPE * nested) {                 \
+    (void)nested;                                                   \
+    PerfettoPbMsgEndNested(&msg->msg);                              \
   }
 
-#define PERFETTO_I_PB_FIELD_PACKED(PROTO, C_TYPE, NAME, NUM)                  \
-  static inline void PERFETTO_I_PB_SETTER_NAME(PROTO, NAME)(                  \
-      struct PROTO * msg, const void* data, size_t len) {                     \
-    PerfettoPbMsgAppendType2Field(                                            \
-        &msg->msg, NUM, PERFETTO_STATIC_CAST(const uint8_t*, data), len);     \
-  }                                                                           \
-  static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PROTO, NAME)(            \
-      struct PROTO * msg, struct PerfettoPbPackedMsg##C_TYPE * nested) {      \
-    struct PerfettoPbMsg* nested_msg =                                        \
-        PERFETTO_REINTERPRET_CAST(struct PerfettoPbMsg*, nested);             \
-    PerfettoPbMsgBeginNested(&msg->msg, nested_msg, NUM);                     \
-  }                                                                           \
-  static inline void PERFETTO_I_PB_SETTER_END_NAME(PROTO, NAME)(              \
-      struct PROTO * msg, struct PerfettoPbPackedMsg##C_TYPE * nested) {      \
-    (void)nested;                                                             \
-    PerfettoPbMsgEndNested(&msg->msg);                                        \
-  }                                                                           \
+#define PERFETTO_I_PB_FIELD_PACKED(PREFIX, PROTO, C_TYPE, NAME, NUM)      \
+  static inline void PERFETTO_I_PB_SETTER_NAME(PREFIX, NAME)(             \
+      struct PROTO * msg, const void* data, size_t len) {                 \
+    PerfettoPbMsgAppendType2Field(                                        \
+        &msg->msg, NUM, PERFETTO_STATIC_CAST(const uint8_t*, data), len); \
+  }                                                                       \
+  static inline void PERFETTO_I_PB_SETTER_BEGIN_NAME(PREFIX, NAME)(       \
+      struct PROTO * msg, struct PerfettoPbPackedMsg##C_TYPE * nested) {  \
+    struct PerfettoPbMsg* nested_msg =                                    \
+        PERFETTO_REINTERPRET_CAST(struct PerfettoPbMsg*, nested);         \
+    PerfettoPbMsgBeginNested(&msg->msg, nested_msg, NUM);                 \
+  }                                                                       \
+  static inline void PERFETTO_I_PB_SETTER_END_NAME(PREFIX, NAME)(         \
+      struct PROTO * msg, struct PerfettoPbPackedMsg##C_TYPE * nested) {  \
+    (void)nested;                                                         \
+    PerfettoPbMsgEndNested(&msg->msg);                                    \
+  }
 
 #define PERFETTO_I_PB_NUM_FIELD(PROTO, NAME, NUM) \
   enum { PERFETTO_I_PB_NUM_FIELD_NAME(PROTO, NAME) = NUM }
@@ -259,10 +259,20 @@
 //        nested): Begins (and ends) a packed helper nested submessage (of the
 //        right type) to allow users to push repeated entries one by one
 //        directly into the stream writer buffer.
-#define PERFETTO_PB_FIELD(PROTO, TYPE, C_TYPE, NAME, NUM) \
-  PERFETTO_I_PB_FIELD_##TYPE(PROTO, C_TYPE, NAME, NUM)    \
+#define PERFETTO_PB_FIELD(PROTO, TYPE, C_TYPE, NAME, NUM)     \
+  PERFETTO_I_PB_FIELD_##TYPE(PROTO, PROTO, C_TYPE, NAME, NUM) \
       PERFETTO_I_PB_NUM_FIELD(PROTO, NAME, NUM)
 
+// Defines accessors for a field of a message for an extension.
+// * `EXTENSION`: The name of the extension. it's going to be used as a prefix.
+//    There doesn't need to be a PERFETTO_PB_MSG definition for this.
+// * `PROTO`: The (base) message that contains this field. This should be the
+//   same identifier passed to PERFETTO_PB_MSG.
+// The rest of the params are the same as the PERFETTO_PB_FIELD macro.
+#define PERFETTO_PB_EXTENSION_FIELD(EXTENSION, PROTO, TYPE, C_TYPE, NAME, NUM) \
+  PERFETTO_I_PB_FIELD_##TYPE(EXTENSION, PROTO, C_TYPE, NAME, NUM)              \
+      PERFETTO_I_PB_NUM_FIELD(EXTENSION, NAME, NUM)
+
 // Defines an enum type nested inside a message (PROTO).
 #define PERFETTO_PB_ENUM_IN_MSG(PROTO, ENUM) \
   enum PERFETTO_I_PB_CONCAT_3(PROTO, _, ENUM)
diff --git a/src/protozero/protoc_plugin/protozero_c_plugin.cc b/src/protozero/protoc_plugin/protozero_c_plugin.cc
index 804968e..de3a99a 100644
--- a/src/protozero/protoc_plugin/protozero_c_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_c_plugin.cc
@@ -94,6 +94,8 @@
       GenerateEnumDescriptor(enumeration);
     for (const Descriptor* message : messages_)
       GenerateMessageDescriptor(message);
+    for (const auto& [name, descriptors] : extensions_)
+      GenerateExtension(name, descriptors);
     GenerateEpilogue();
     return error_.empty();
   }
@@ -240,7 +242,8 @@
           // As the support for extensions in protozero is limited, the code
           // assumes that extend blocks are located inside a wrapper message and
           // name of this message is used to group them.
-          std::string extension_name = extension->extension_scope()->name();
+          std::string extension_name =
+              GetCppClassName(extension->extension_scope());
           extensions_[extension_name].push_back(extension);
         }
       } else {
@@ -449,7 +452,7 @@
 
   // Packed repeated fields are encoded as a length-delimited field on the wire,
   // where the payload is the concatenation of invidually encoded elements.
-  void GeneratePackedRepeatedFieldDescriptor(
+  void GeneratePackedRepeatedFieldDescriptorArgs(
       const std::string& message_cpp_type,
       const FieldDescriptor* field) {
     std::map<std::string, std::string> setter;
@@ -457,13 +460,29 @@
     setter["name"] = field->lowercase_name();
     setter["class"] = message_cpp_type;
     setter["buffer_type"] = FieldTypeToPackedBufferType(field->type());
-    stub_h_->Print(
-        setter,
-        "PERFETTO_PB_FIELD($class$, PACKED, $buffer_type$, $name$, $id$);\n");
+    stub_h_->Print(setter, "$class$, PACKED, $buffer_type$, $name$, $id$\n");
   }
 
-  void GenerateSimpleFieldDescriptor(const std::string& message_cpp_type,
-                                     const FieldDescriptor* field) {
+  void GeneratePackedRepeatedFieldDescriptor(
+      const std::string& message_cpp_type,
+      const FieldDescriptor* field) {
+    stub_h_->Print("PERFETTO_PB_FIELD(");
+    GeneratePackedRepeatedFieldDescriptorArgs(message_cpp_type, field);
+    stub_h_->Print(");\n");
+  }
+
+  void GeneratePackedRepeatedFieldDescriptorForExtension(
+      const std::string& field_cpp_prefix,
+      const std::string& message_cpp_type,
+      const FieldDescriptor* field) {
+    stub_h_->Print("PERFETTO_PB_EXTENSION_FIELD($prefix$, ", "prefix",
+                   field_cpp_prefix);
+    GeneratePackedRepeatedFieldDescriptorArgs(message_cpp_type, field);
+    stub_h_->Print(");\n");
+  }
+
+  void GenerateSimpleFieldDescriptorArgs(const std::string& message_cpp_type,
+                                         const FieldDescriptor* field) {
     std::map<std::string, std::string> setter;
     setter["id"] = std::to_string(field->number());
     setter["name"] = field->lowercase_name();
@@ -473,9 +492,7 @@
     switch (field->type()) {
       case FieldDescriptor::TYPE_BYTES:
       case FieldDescriptor::TYPE_STRING:
-        stub_h_->Print(
-            setter,
-            "PERFETTO_PB_FIELD($class$, STRING, const char*, $name$, $id$);\n");
+        stub_h_->Print(setter, "$class$, STRING, const char*, $name$, $id$");
         break;
       case FieldDescriptor::TYPE_UINT64:
       case FieldDescriptor::TYPE_UINT32:
@@ -483,29 +500,21 @@
       case FieldDescriptor::TYPE_INT32:
       case FieldDescriptor::TYPE_BOOL:
       case FieldDescriptor::TYPE_ENUM:
-        stub_h_->Print(
-            setter,
-            "PERFETTO_PB_FIELD($class$, VARINT, $ctype$, $name$, $id$);\n");
+        stub_h_->Print(setter, "$class$, VARINT, $ctype$, $name$, $id$");
         break;
       case FieldDescriptor::TYPE_SINT64:
       case FieldDescriptor::TYPE_SINT32:
-        stub_h_->Print(
-            setter,
-            "PERFETTO_PB_FIELD($class$, ZIGZAG, $ctype$, $name$, $id$);\n");
+        stub_h_->Print(setter, "$class$, ZIGZAG, $ctype$, $name$, $id$");
         break;
       case FieldDescriptor::TYPE_SFIXED32:
       case FieldDescriptor::TYPE_FIXED32:
       case FieldDescriptor::TYPE_FLOAT:
-        stub_h_->Print(
-            setter,
-            "PERFETTO_PB_FIELD($class$, FIXED32, $ctype$, $name$, $id$);\n");
+        stub_h_->Print(setter, "$class$, FIXED32, $ctype$, $name$, $id$");
         break;
       case FieldDescriptor::TYPE_SFIXED64:
       case FieldDescriptor::TYPE_FIXED64:
       case FieldDescriptor::TYPE_DOUBLE:
-        stub_h_->Print(
-            setter,
-            "PERFETTO_PB_FIELD($class$, FIXED64, $ctype$, $name$, $id$);\n");
+        stub_h_->Print(setter, "$class$, FIXED64, $ctype$, $name$, $id$");
         break;
       case FieldDescriptor::TYPE_MESSAGE:
       case FieldDescriptor::TYPE_GROUP:
@@ -514,6 +523,23 @@
     }
   }
 
+  void GenerateSimpleFieldDescriptor(const std::string& message_cpp_type,
+                                     const FieldDescriptor* field) {
+    stub_h_->Print("PERFETTO_PB_FIELD(");
+    GenerateSimpleFieldDescriptorArgs(message_cpp_type, field);
+    stub_h_->Print(");\n");
+  }
+
+  void GenerateSimpleFieldDescriptorForExtension(
+      const std::string& field_cpp_prefix,
+      const std::string& message_cpp_type,
+      const FieldDescriptor* field) {
+    stub_h_->Print("PERFETTO_PB_EXTENSION_FIELD($prefix$, ", "prefix",
+                   field_cpp_prefix);
+    GenerateSimpleFieldDescriptorArgs(message_cpp_type, field);
+    stub_h_->Print(");\n");
+  }
+
   void GenerateNestedMessageFieldDescriptor(const std::string& message_cpp_type,
                                             const FieldDescriptor* field) {
     std::string inner_class = GetCppClassName(field->message_type());
@@ -523,6 +549,19 @@
         "name", field->lowercase_name(), "inner_class", inner_class);
   }
 
+  void GenerateNestedMessageFieldDescriptorForExtension(
+      const std::string& field_cpp_prefix,
+      const std::string& message_cpp_type,
+      const FieldDescriptor* field) {
+    std::string inner_class = GetCppClassName(field->message_type());
+    stub_h_->Print(
+        "PERFETTO_PB_EXTENSION_FIELD($prefix$, $class$, MSG, $inner_class$, "
+        "$name$, $id$);\n",
+        "prefix", field_cpp_prefix, "class", message_cpp_type, "id",
+        std::to_string(field->number()), "name", field->lowercase_name(),
+        "inner_class", inner_class);
+  }
+
   void GenerateMessageDescriptor(const Descriptor* message) {
     stub_h_->Print("PERFETTO_PB_MSG($name$);\n", "name",
                    GetCppClassName(message));
@@ -546,6 +585,60 @@
     }
   }
 
+  void GenerateExtensionFieldDescriptor(const std::string& field_cpp_prefix,
+                                        const std::string& message_cpp_type,
+                                        const FieldDescriptor* field) {
+    // GenerateFieldMetadata(message_cpp_type, field);
+    if (field->is_packed()) {
+      GeneratePackedRepeatedFieldDescriptorForExtension(
+          field_cpp_prefix, message_cpp_type, field);
+    } else if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
+      GenerateSimpleFieldDescriptorForExtension(field_cpp_prefix,
+                                                message_cpp_type, field);
+    } else {
+      GenerateNestedMessageFieldDescriptorForExtension(field_cpp_prefix,
+                                                       message_cpp_type, field);
+    }
+  }
+
+  // Generate extension class for a group of FieldDescriptor instances
+  // representing one "extend" block in proto definition. For example:
+  //
+  //   message SpecificExtension {
+  //     extend GeneralThing {
+  //       optional Fizz fizz = 101;
+  //       optional Buzz buzz = 102;
+  //     }
+  //   }
+  //
+  // This is going to be passed as a vector of two elements, "fizz" and
+  // "buzz". Wrapping message is used to provide a name for generated
+  // extension class.
+  //
+  // In the example above, generated code is going to look like:
+  //
+  //   class SpecificExtension : public GeneralThing {
+  //     Fizz* set_fizz();
+  //     Buzz* set_buzz();
+  //   }
+  void GenerateExtension(
+      const std::string& extension_name,
+      const std::vector<const FieldDescriptor*>& descriptors) {
+    // Use an arbitrary descriptor in order to get generic information not
+    // specific to any of them.
+    const FieldDescriptor* descriptor = descriptors[0];
+    const Descriptor* base_message = descriptor->containing_type();
+
+    for (const FieldDescriptor* field : descriptors) {
+      if (field->containing_type() != base_message) {
+        Abort("one wrapper should extend only one message");
+        return;
+      }
+      GenerateExtensionFieldDescriptor(extension_name,
+                                       GetCppClassName(base_message), field);
+    }
+  }
+
   void GenerateEpilogue() {
     stub_h_->Print("#endif  // $guard$\n", "guard", GenerateGuard());
   }
diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc
index 975f001..802d54a 100644
--- a/src/shared_lib/test/api_integrationtest.cc
+++ b/src/shared_lib/test/api_integrationtest.cc
@@ -40,6 +40,7 @@
 #include "test/gtest_and_gmock.h"
 
 #include "src/shared_lib/reset_for_testing.h"
+#include "src/shared_lib/test/protos/extensions.pzc.h"
 #include "src/shared_lib/test/protos/test_messages.pzc.h"
 #include "src/shared_lib/test/utils.h"
 
@@ -396,6 +397,42 @@
                   VarIntField(1000)))))));
 }
 
+TEST_F(SharedLibProtozeroSerializationTest, Extensions) {
+  struct protozero_test_protos_RealFakeEvent base;
+  PerfettoPbMsgInit(&base.msg, &writer);
+
+  {
+    struct protozero_test_protos_SystemA msg_a;
+    protozero_test_protos_BrowserExtension_begin_extension_a(&base, &msg_a);
+    protozero_test_protos_SystemA_set_cstr_string_a(&msg_a, "str_a");
+    protozero_test_protos_BrowserExtension_end_extension_a(&base, &msg_a);
+  }
+  {
+    struct protozero_test_protos_SystemB msg_b;
+    protozero_test_protos_BrowserExtension_begin_extension_b(&base, &msg_b);
+    protozero_test_protos_SystemB_set_cstr_string_b(&msg_b, "str_b");
+    protozero_test_protos_BrowserExtension_end_extension_b(&base, &msg_b);
+  }
+
+  protozero_test_protos_RealFakeEvent_set_cstr_base_string(&base, "str");
+
+  EXPECT_THAT(
+      FieldView(GetData()),
+      ElementsAre(
+          PbField(
+              protozero_test_protos_BrowserExtension_extension_a_field_number,
+              MsgField(ElementsAre(
+                  PbField(protozero_test_protos_SystemA_string_a_field_number,
+                          StringField("str_a"))))),
+          PbField(
+              protozero_test_protos_BrowserExtension_extension_b_field_number,
+              MsgField(ElementsAre(
+                  PbField(protozero_test_protos_SystemB_string_b_field_number,
+                          StringField("str_b"))))),
+          PbField(protozero_test_protos_RealFakeEvent_base_string_field_number,
+                  StringField("str"))));
+}
+
 TEST_F(SharedLibProtozeroSerializationTest, PackedRepeatedMsgVarInt) {
   struct protozero_test_protos_PackedRepeatedFields msg;
   PerfettoPbMsgInit(&msg.msg, &writer);
diff --git a/src/shared_lib/test/protos/BUILD.gn b/src/shared_lib/test/protos/BUILD.gn
index 434f4a5..21fdeaf 100644
--- a/src/shared_lib/test/protos/BUILD.gn
+++ b/src/shared_lib/test/protos/BUILD.gn
@@ -15,6 +15,7 @@
 source_set("protos") {
   testonly = true
   sources = [
+    "extensions.pzc.h",
     "library.pzc.h",
     "library_internals/galaxies.pzc.h",
     "test_messages.pzc.h",
diff --git a/src/shared_lib/test/protos/extensions.pzc.h b/src/shared_lib/test/protos/extensions.pzc.h
new file mode 100644
index 0000000..996a11a
--- /dev/null
+++ b/src/shared_lib/test/protos/extensions.pzc.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
+#ifndef SRC_SHARED_LIB_TEST_PROTOS_EXTENSIONS_PZC_H_
+#define SRC_SHARED_LIB_TEST_PROTOS_EXTENSIONS_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_MSG(protozero_test_protos_SystemB);
+PERFETTO_PB_FIELD(protozero_test_protos_SystemB, VARINT, uint32_t, int_b, 1);
+PERFETTO_PB_FIELD(protozero_test_protos_SystemB,
+                  STRING,
+                  const char*,
+                  string_b,
+                  2);
+
+PERFETTO_PB_MSG(protozero_test_protos_SystemA);
+PERFETTO_PB_FIELD(protozero_test_protos_SystemA, VARINT, uint32_t, int_a, 1);
+PERFETTO_PB_FIELD(protozero_test_protos_SystemA,
+                  STRING,
+                  const char*,
+                  string_a,
+                  2);
+
+PERFETTO_PB_MSG(protozero_test_protos_RealFakeEvent);
+PERFETTO_PB_FIELD(protozero_test_protos_RealFakeEvent,
+                  VARINT,
+                  uint32_t,
+                  base_int,
+                  1);
+PERFETTO_PB_FIELD(protozero_test_protos_RealFakeEvent,
+                  STRING,
+                  const char*,
+                  base_string,
+                  2);
+
+PERFETTO_PB_EXTENSION_FIELD(protozero_test_protos_BrowserExtension,
+                            protozero_test_protos_RealFakeEvent,
+                            MSG,
+                            protozero_test_protos_SystemA,
+                            extension_a,
+                            10);
+PERFETTO_PB_EXTENSION_FIELD(protozero_test_protos_BrowserExtension,
+                            protozero_test_protos_RealFakeEvent,
+                            MSG,
+                            protozero_test_protos_SystemB,
+                            extension_b,
+                            11);
+#endif  // SRC_SHARED_LIB_TEST_PROTOS_EXTENSIONS_PZC_H_
diff --git a/tools/gen_c_protos b/tools/gen_c_protos
index cefc5e3..c62a48f 100755
--- a/tools/gen_c_protos
+++ b/tools/gen_c_protos
@@ -48,6 +48,7 @@
   },
   {
     'files': [
+      'src/protozero/test/example_proto/extensions.proto',
       'src/protozero/test/example_proto/library.proto',
       'src/protozero/test/example_proto/library_internals/galaxies.proto',
       'src/protozero/test/example_proto/other_package/test_messages.proto',