Add a new upb_Message_Convert API that converts a upb message from one minitable type to another compatible one (e.g. they are both derived from the same proto definition via tree-shaking).

PiperOrigin-RevId: 922780263
This commit is contained in:
Ada Zhang 2026-05-28 08:11:37 -07:00 committed by Copybara-Service
parent 1eee6a6b02
commit 0982838348
8 changed files with 2456 additions and 1 deletions

View file

@ -178,6 +178,7 @@ upb/message/accessors.h
upb/message/array.h
upb/message/compare.h
upb/message/compat.h
upb/message/convert.h
upb/message/copy.h
upb/message/map.h
upb/message/map_gencode_util.h

View file

@ -244,6 +244,7 @@ cc_dist_library(
"//upb:generated_code_support",
"//upb/json",
"//upb/message:compare",
"//upb/message:convert",
"//upb/message:copy",
"//upb/mini_table:compat",
"//upb/mini_table:debug_string",

View file

@ -198,6 +198,63 @@ cc_library(
],
)
# Experimental API, subject to change.
cc_library(
name = "convert",
srcs = [
"convert.c",
],
hdrs = [
"convert.h",
],
copts = UPB_DEFAULT_COPTS,
visibility = [
"//upb:__pkg__",
"//upb:friends",
],
deps = [
":compare",
":internal",
":message",
"//upb/base",
"//upb/mem",
"//upb/mini_table",
"//upb/mini_table:internal",
"//upb/port",
"//upb/wire",
"//upb/wire:back_alloc",
"//upb/wire:decoder",
"//upb/wire:encoder",
"//upb/wire:eps_copy_input_stream",
],
)
cc_test(
name = "convert_test",
srcs = ["convert_test.cc"],
deps = [
":convert",
":convert_test_upb_minitable_proto",
":convert_test_upb_proto",
":internal",
":message",
":message_test_upb_minitable_proto",
":message_test_upb_proto",
":promote",
"//upb/base",
"//upb/mem",
"//upb/mini_table",
"//upb/port",
"//upb/test:test_messages_proto2_upb_proto",
"//upb/test:test_messages_proto3_upb_minitable",
"//upb/test:test_messages_proto3_upb_proto",
"//upb/test:test_upb_proto",
"//upb/wire",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
)
cc_test(
name = "merge_test",
srcs = ["merge_test.cc"],
@ -416,6 +473,7 @@ cc_test(
features = UPB_DEFAULT_FEATURES,
deps = [
":compare",
":convert",
":internal",
":message",
":message_test_upb_minitable_proto",
@ -517,7 +575,10 @@ filegroup(
[
"**/*test.cc",
],
exclude = ["promote_test.cc"],
exclude = [
"promote_test.cc",
"convert_fuzz_test.cc",
],
),
visibility = ["//upb:__pkg__"],
)
@ -531,3 +592,21 @@ filegroup(
),
visibility = ["//upb:__pkg__"],
)
proto_library(
name = "convert_test_proto",
testonly = 1,
srcs = ["convert_test.proto"],
)
upb_minitable_proto_library(
name = "convert_test_upb_minitable_proto",
testonly = 1,
deps = [":convert_test_proto"],
)
upb_c_proto_library(
name = "convert_test_upb_proto",
testonly = 1,
deps = [":convert_test_proto"],
)

646
upb/message/convert.c Normal file
View file

@ -0,0 +1,646 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "upb/message/convert.h"
#include <stdint.h>
#include <string.h>
#include "upb/base/descriptor_constants.h"
#include "upb/base/error_handler.h"
#include "upb/base/string_view.h"
#include "upb/mem/arena.h"
#include "upb/message/accessors.h"
#include "upb/message/array.h"
#include "upb/message/compare.h"
#include "upb/message/internal/accessors.h"
#include "upb/message/internal/message.h"
#include "upb/message/map.h"
#include "upb/message/message.h"
#include "upb/mini_table/enum.h"
#include "upb/mini_table/extension.h"
#include "upb/mini_table/extension_registry.h"
#include "upb/mini_table/field.h"
#include "upb/mini_table/internal/message.h"
#include "upb/mini_table/message.h"
#include "upb/wire/decode.h"
#include "upb/wire/encode.h"
#include "upb/wire/eps_copy_input_stream.h"
#include "upb/wire/internal/back_alloc.h"
#include "upb/wire/internal/decoder.h"
#include "upb/wire/internal/encoder.h"
// Must be last.
#include "upb/port/def.inc"
typedef struct {
upb_Decoder decoder;
upb_encstate encoder;
upb_Arena* arena;
upb_ErrorHandler err;
} upb_Converter;
// Minitable compatibility type check on the field, but not the
// submessage. Note: this check always succeeds for enums, whether the
// enum is open or closed.
UPB_INLINE bool _upb_MiniTableField_IsCompatible(
const upb_MiniTableField* src_f, const upb_MiniTableField* dst_f) {
return upb_MiniTableField_Type(src_f) == upb_MiniTableField_Type(dst_f) &&
upb_MiniTableField_IsArray(src_f) ==
upb_MiniTableField_IsArray(dst_f) &&
upb_MiniTableField_IsMap(src_f) == upb_MiniTableField_IsMap(dst_f);
}
UPB_INLINE bool _upb_MiniTableField_IsMapEntryCompatible(
const upb_MiniTableField* src_f, const upb_MiniTableField* dst_f) {
const upb_MiniTable* src_entry_mt = upb_MiniTable_MapEntrySubMessage(src_f);
const upb_MiniTable* dst_entry_mt = upb_MiniTable_MapEntrySubMessage(dst_f);
if (src_entry_mt == dst_entry_mt) return true;
return _upb_MiniTableField_IsCompatible(upb_MiniTable_MapKey(src_entry_mt),
upb_MiniTable_MapKey(dst_entry_mt)) &&
_upb_MiniTableField_IsCompatible(upb_MiniTable_MapValue(src_entry_mt),
upb_MiniTable_MapValue(dst_entry_mt));
}
UPB_INLINE bool _upb_MiniTableField_IsExtensionCompatible(
const upb_MiniTableField* src_f, const upb_MiniTableField* dst_f) {
UPB_ASSERT(!upb_MiniTableField_IsMap(src_f));
if (upb_MiniTableField_IsMap(dst_f)) return false;
return upb_MiniTableField_Type(dst_f) == upb_MiniTableField_Type(src_f) &&
upb_MiniTableField_IsArray(dst_f) == upb_MiniTableField_IsArray(src_f);
}
static void upb_Message_SetFieldOrExtension(upb_Message* msg,
const upb_MiniTableField* f,
const upb_MiniTableExtension* ext,
const upb_MessageValue* val,
upb_Arena* arena) {
if (ext != NULL) {
upb_Message_SetExtension(msg, ext, val, arena);
} else {
upb_Message_SetBaseField(msg, f, val);
}
}
static void upb_Message_EncodeFieldAsUnknown(
upb_encstate* e, upb_Message* dst, const upb_Message* src,
const upb_MiniTableField* src_field, int depth, upb_ErrorHandler* err) {
size_t size;
int encode_options = upb_Encode_LimitDepth(0, depth);
char* buf = upb_BackAlloc_Init(&e->alloc, e->alloc.arena);
UPB_PRIVATE(_upb_Encode_Field)(e, src, src_field, &buf, &size,
encode_options);
if (size > 0) {
if (!UPB_PRIVATE(_upb_Message_AddUnknown)(dst, buf, size, e->alloc.arena,
kUpb_AddUnknown_Alias)) {
upb_ErrorHandler_ThrowError(err, kUpb_ErrorCode_OutOfMemory);
}
}
}
static void upb_Message_EncodeExtensionAsUnknown(
upb_encstate* e, upb_Message* dst, const upb_MiniTable* dst_mt,
const upb_MiniTableExtension* ext, upb_MessageValue val, int depth,
upb_ErrorHandler* err) {
size_t size;
int encode_options = upb_Encode_LimitDepth(0, depth);
bool is_message_set = upb_MiniTable_IsMessageSet(dst_mt);
char* buf = upb_BackAlloc_Init(&e->alloc, e->alloc.arena);
UPB_PRIVATE(_upb_Encode_Extension)(e, ext, val, is_message_set, &buf, &size,
encode_options);
if (size > 0) {
if (!UPB_PRIVATE(_upb_Message_AddUnknown)(dst, buf, size, e->alloc.arena,
kUpb_AddUnknown_Alias)) {
upb_ErrorHandler_ThrowError(err, kUpb_ErrorCode_OutOfMemory);
}
}
}
static void upb_Message_ConvertInternal(upb_Converter* c, upb_Message* dst,
const upb_Message* src,
const upb_MiniTable* dst_mt,
const upb_MiniTable* src_mt,
const upb_ExtensionRegistry* extreg,
int depth);
static void upb_Array_DeepConvert(
upb_Converter* c, upb_Array* dst, const upb_Array* src,
const upb_MiniTable* dst_sub_mt, const upb_MiniTable* src_sub_mt,
const upb_MiniTableField* dst_f, upb_Message* dst_msg,
const upb_ExtensionRegistry* extreg, int depth) {
size_t size = upb_Array_Size(src);
if (!upb_Array_Resize(dst, size, c->arena)) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
size_t dst_i = 0;
for (size_t i = 0; i < size; ++i) {
upb_MessageValue src_val = upb_Array_Get(src, i);
if (upb_MiniTableField_IsClosedEnum(dst_f)) {
const upb_MiniTableEnum* dst_e = upb_MiniTable_GetSubEnumTable(dst_f);
if (upb_MiniTableEnum_CheckValue(dst_e, src_val.int32_val)) {
upb_MessageValue dst_val;
dst_val.int32_val = src_val.int32_val;
upb_Array_Set(dst, dst_i++, dst_val);
} else if (!_upb_Encoder_AddEnumValueToUnknown(
dst_msg, dst_f, src_val.int32_val, c->arena)) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
} else {
const upb_Message* src_msg = src_val.msg_val;
upb_Message* dst_sub = upb_Message_New(dst_sub_mt, c->arena);
if (!dst_sub) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
upb_Message_ConvertInternal(c, dst_sub, src_msg, dst_sub_mt, src_sub_mt,
extreg, depth);
upb_MessageValue dst_val;
dst_val.msg_val = dst_sub;
upb_Array_Set(dst, dst_i++, dst_val);
}
}
if (dst_i != size) {
upb_Array_Resize(dst, dst_i, c->arena);
}
}
static bool upb_Message_ConvertArrayField(upb_Converter* c, upb_Message* dst,
const upb_Message* src,
const upb_MiniTableField* dst_f,
const upb_MiniTableField* src_f,
const upb_ExtensionRegistry* extreg,
int depth) {
const upb_Array* src_arr = upb_Message_GetArray(src, src_f);
if (!src_arr) return true;
const upb_MiniTable* dst_sub_mt = upb_MiniTable_SubMessage(dst_f);
const upb_MiniTable* src_sub_mt = upb_MiniTable_SubMessage(src_f);
if (dst_sub_mt != src_sub_mt || upb_MiniTableField_IsClosedEnum(dst_f)) {
upb_Array* dst_arr =
upb_Array_New(c->arena, upb_MiniTableField_CType(dst_f));
if (!dst_arr)
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
upb_Array_DeepConvert(c, dst_arr, src_arr, dst_sub_mt, src_sub_mt, dst_f,
dst, extreg, depth);
upb_Message_SetBaseField(dst, dst_f, &dst_arr);
return true;
}
return false;
}
static void upb_Map_DeepConvert(
upb_Converter* c, upb_Map* dst, const upb_Map* src,
const upb_MiniTable* dst_entry_mt, const upb_MiniTable* src_entry_mt,
const upb_MiniTableField* dst_map_f, upb_Message* dst_msg,
const upb_ExtensionRegistry* extreg, int depth) {
const upb_MiniTableField* dst_val_f = upb_MiniTable_MapValue(dst_entry_mt);
const upb_MiniTable* dst_val_mt = upb_MiniTable_SubMessage(dst_val_f);
const upb_MiniTableField* src_val_f = upb_MiniTable_MapValue(src_entry_mt);
const upb_MiniTable* src_val_mt = upb_MiniTable_SubMessage(src_val_f);
size_t iter = kUpb_Map_Begin;
upb_MessageValue key, src_val;
while (upb_Map_Next(src, &key, &src_val, &iter)) {
if (dst_val_mt && src_val_mt) {
const upb_Message* src_msg = src_val.msg_val;
upb_Message* dst_sub = upb_Message_New(dst_val_mt, c->arena);
if (!dst_sub) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
upb_Message_ConvertInternal(c, dst_sub, src_msg, dst_val_mt, src_val_mt,
extreg, depth);
upb_MessageValue dst_val;
dst_val.msg_val = dst_sub;
if (!upb_Map_Set(dst, key, dst_val, c->arena)) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
} else {
// Scalar value.
if (upb_MiniTableField_IsClosedEnum(dst_val_f)) {
const upb_MiniTableEnum* dst_e =
upb_MiniTable_GetSubEnumTable(dst_val_f);
if (upb_MiniTableEnum_CheckValue(dst_e, src_val.int32_val)) {
if (!upb_Map_Set(dst, key, src_val, c->arena)) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
} else {
upb_Message* ent_msg = upb_Message_New(src_entry_mt, c->arena);
if (!ent_msg) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
upb_Message_SetBaseField(ent_msg, upb_MiniTable_MapKey(src_entry_mt),
&key);
upb_Message_SetBaseField(
ent_msg, upb_MiniTable_MapValue(src_entry_mt), &src_val);
_upb_Encoder_AddMapEntryUnknown(dst_msg, dst_map_f, ent_msg,
src_entry_mt, c->arena);
}
} else {
if (!upb_Map_Set(dst, key, src_val, c->arena)) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
}
}
}
}
static bool upb_Message_ConvertMapField(upb_Converter* c, upb_Message* dst,
const upb_Message* src,
const upb_MiniTableField* dst_f,
const upb_MiniTableField* src_f,
const upb_ExtensionRegistry* extreg,
int depth) {
const upb_Map* src_map = upb_Message_GetMap(src, src_f);
if (!src_map) return true;
const upb_MiniTable* dst_entry_mt = upb_MiniTable_MapEntrySubMessage(dst_f);
const upb_MiniTable* src_entry_mt = upb_MiniTable_MapEntrySubMessage(src_f);
if (dst_entry_mt != src_entry_mt || upb_MiniTableField_IsClosedEnum(dst_f)) {
const upb_MiniTableField* dst_val_f = upb_MiniTable_MapValue(dst_entry_mt);
upb_Map* dst_map = upb_Map_New(
c->arena, upb_MiniTableField_CType(upb_MiniTable_MapKey(dst_entry_mt)),
upb_MiniTableField_CType(dst_val_f));
if (!dst_map) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
upb_Map_DeepConvert(c, dst_map, src_map, dst_entry_mt, src_entry_mt, dst_f,
dst, extreg, depth);
upb_Message_SetBaseField(dst, dst_f, &dst_map);
return true;
}
return false;
}
static void upb_Message_ConvertField(upb_Converter* c, upb_Message* dst,
const upb_Message* src,
const upb_MiniTableField* dst_f,
const upb_MiniTableField* src_f,
const upb_ExtensionRegistry* extreg,
int depth) {
if (upb_MiniTableField_HasPresence(src_f)) {
if (!upb_Message_HasBaseField(src, src_f)) return;
} else if (upb_MiniTableField_IsScalar(src_f)) {
// For proto3 implicit scalar fields, we only need to copy if the source
// field is set.
const void* src_data = UPB_PRIVATE(_upb_Message_DataPtr)(src, src_f);
if (UPB_PRIVATE(_upb_MiniTableField_DataIsZero)(src_f, src_data)) return;
}
if (upb_MiniTableField_CType(dst_f) == kUpb_CType_Message) {
if (upb_MiniTableField_IsScalar(dst_f)) {
const upb_Message* src_sub = upb_Message_GetMessage(src, src_f);
if (!src_sub) return;
const upb_MiniTable* dst_sub_mt = upb_MiniTable_SubMessage(dst_f);
const upb_MiniTable* src_sub_mt = upb_MiniTable_SubMessage(src_f);
if (dst_sub_mt == src_sub_mt) {
upb_Message_SetMessage(dst, dst_f, (upb_Message*)src_sub);
return;
}
upb_Message* dst_sub =
upb_Message_GetOrCreateMutableMessage(dst, dst_f, c->arena);
if (!dst_sub)
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
upb_Message_ConvertInternal(c, dst_sub, src_sub, dst_sub_mt, src_sub_mt,
extreg, depth);
return;
} else if (upb_MiniTableField_IsArray(dst_f)) {
if (upb_Message_ConvertArrayField(c, dst, src, dst_f, src_f, extreg,
depth)) {
return;
}
} else if (upb_MiniTableField_IsMap(dst_f)) {
if (UPB_UNLIKELY(
!_upb_MiniTableField_IsMapEntryCompatible(src_f, dst_f))) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_Malformed);
}
if (upb_Message_ConvertMapField(c, dst, src, dst_f, src_f, extreg,
depth)) {
return;
}
}
} else if (upb_MiniTableField_IsClosedEnum(dst_f)) {
if (upb_MiniTableField_IsArray(dst_f)) {
if (upb_Message_ConvertArrayField(c, dst, src, dst_f, src_f, extreg,
depth)) {
return;
}
} else if (upb_MiniTableField_IsMap(dst_f)) {
if (upb_Message_ConvertMapField(c, dst, src, dst_f, src_f, extreg,
depth)) {
return;
}
} else {
int32_t val;
memcpy(&val, UPB_PRIVATE(_upb_Message_DataPtr)(src, src_f), 4);
const upb_MiniTableEnum* dst_e = upb_MiniTable_GetSubEnumTable(dst_f);
if (!upb_MiniTableEnum_CheckValue(dst_e, val)) {
if (!_upb_Encoder_AddEnumValueToUnknown(dst, dst_f, val, c->arena)) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
return;
}
}
}
UPB_PRIVATE(_upb_MiniTableField_DataCopy)
(dst_f, UPB_PRIVATE(_upb_Message_MutableDataPtr)(dst, dst_f),
UPB_PRIVATE(_upb_Message_DataPtr)(src, src_f));
if (upb_MiniTableField_HasPresence(dst_f)) {
UPB_PRIVATE(_upb_Message_SetPresence)(dst, dst_f);
}
}
static void upb_Message_ConvertExtensions(upb_Converter* c, upb_Message* dst,
const upb_Message* src,
const upb_MiniTable* dst_mt,
const upb_ExtensionRegistry* extreg,
int depth) {
const upb_MiniTableExtension* ext;
upb_MessageValue val;
uintptr_t iter = kUpb_Message_ExtensionBegin;
while (upb_Message_NextExtension(src, &ext, &val, &iter)) {
const upb_MiniTableField* dst_f = upb_MiniTable_FindFieldByNumber(
dst_mt, upb_MiniTableExtension_Number(ext));
const upb_MiniTableExtension* dst_ext = NULL;
if (!dst_f) {
// Source extension not found in the destination schema. Check the
// extension registry.
if (extreg != NULL) {
dst_ext = upb_ExtensionRegistry_Lookup(
extreg, dst_mt, upb_MiniTableExtension_Number(ext));
if (dst_ext) {
dst_f = upb_MiniTableExtension_ToField(dst_ext);
}
}
}
if (dst_f) {
const upb_MiniTableField* src_f = upb_MiniTableExtension_ToField(ext);
UPB_ASSERT(!upb_MiniTableField_IsMap(src_f));
if (UPB_UNLIKELY(
!_upb_MiniTableField_IsExtensionCompatible(src_f, dst_f))) {
// Return an error due to type mismatch.
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_Malformed);
}
if (upb_MiniTableField_CType(dst_f) == kUpb_CType_Message) {
const upb_MiniTable* dst_sub_mt = upb_MiniTable_SubMessage(dst_f);
const upb_MiniTable* src_sub_mt = upb_MiniTable_SubMessage(src_f);
if (upb_MiniTableField_IsArray(dst_f)) {
if (dst_sub_mt != src_sub_mt) {
// Array of messages, and the sub message types differ. Perform
// conversion.
upb_Array* dst_arr = upb_Array_New(c->arena, kUpb_CType_Message);
if (!dst_arr)
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
upb_Array_DeepConvert(c, dst_arr, val.array_val, dst_sub_mt,
src_sub_mt, dst_f, dst, extreg, depth);
upb_MessageValue valid_val;
valid_val.array_val = dst_arr;
upb_Message_SetFieldOrExtension(dst, dst_f, dst_ext, &valid_val,
c->arena);
} else {
// Array of messages, and the sub message types are the same.
// Shallow copy.
upb_Message_SetFieldOrExtension(dst, dst_f, dst_ext, &val,
c->arena);
}
} else if (dst_sub_mt == src_sub_mt) {
// Scalar message, and the message types are the same.
// Shallow copy.
upb_Message_SetFieldOrExtension(dst, dst_f, dst_ext, &val, c->arena);
} else {
// Scalar message, and the message types differ. Perform conversion.
upb_Message* dst_sub = upb_Message_New(dst_sub_mt, c->arena);
if (!dst_sub)
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
upb_Message_ConvertInternal(c, dst_sub, val.msg_val, dst_sub_mt,
src_sub_mt, extreg, depth);
upb_MessageValue valid_val;
valid_val.msg_val = dst_sub;
upb_Message_SetFieldOrExtension(dst, dst_f, dst_ext, &valid_val,
c->arena);
}
} else {
// Scalar non-message type.
if (upb_MiniTableField_IsClosedEnum(dst_f)) {
if (upb_MiniTableField_IsArray(dst_f)) {
upb_Array* dst_arr = upb_Array_New(c->arena, kUpb_CType_Int32);
if (!dst_arr)
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
upb_Array_DeepConvert(c, dst_arr, val.array_val, NULL, NULL, dst_f,
dst, extreg, depth);
upb_MessageValue valid_val;
valid_val.array_val = dst_arr;
upb_Message_SetFieldOrExtension(dst, dst_f, dst_ext, &valid_val,
c->arena);
continue;
} else {
const upb_MiniTableEnum* dst_e =
dst_ext ? upb_MiniTableExtension_GetSubEnum(dst_ext)
: upb_MiniTable_GetSubEnumTable(dst_f);
if (!upb_MiniTableEnum_CheckValue(dst_e, val.int32_val)) {
if (!_upb_Encoder_AddEnumValueToUnknown(dst, dst_f, val.int32_val,
c->arena)) {
upb_ErrorHandler_ThrowError(&c->err,
kUpb_ErrorCode_OutOfMemory);
}
continue;
}
}
}
upb_Message_SetFieldOrExtension(dst, dst_f, dst_ext, &val, c->arena);
}
} else {
// Extension not found in the destination schema.
if (dst_mt->UPB_PRIVATE(ext) == kUpb_ExtMode_NonExtendable) {
// Destination message does not support extensions. Encode the extension
// as an unknown field in the destination message.
upb_Message_EncodeExtensionAsUnknown(&c->encoder, dst, dst_mt, ext, val,
depth, &c->err);
} else if (!upb_Message_SetExtension(dst, ext, &val, c->arena)) {
// Destination message supports extensions. Since this extension is not
// known in the destination schema, we simply carry over the source
// extension.
// TODO - b/510055656: to handle the non-canonical extension properly.
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_OutOfMemory);
}
}
}
}
static void upb_Message_ConvertInternal(upb_Converter* c, upb_Message* dst,
const upb_Message* src,
const upb_MiniTable* dst_mt,
const upb_MiniTable* src_mt,
const upb_ExtensionRegistry* extreg,
int depth) {
UPB_ASSERT(dst != NULL);
if (--depth == 0) {
// TODO:b/494593478 - Add a new error code for max depth exceeded.
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_Malformed);
}
const upb_MiniTableField* dst_f = NULL;
const upb_MiniTableField* dst_first = NULL;
const upb_MiniTableField* src_f = NULL;
const upb_MiniTableField* src_first = NULL;
if (upb_MiniTable_FieldCount(dst_mt) > 0) {
dst_first = upb_MiniTable_GetFieldByIndex(dst_mt, 0);
dst_f = dst_first + upb_MiniTable_FieldCount(dst_mt);
}
if (upb_MiniTable_FieldCount(src_mt) > 0) {
src_first = upb_MiniTable_GetFieldByIndex(src_mt, 0);
src_f = src_first + upb_MiniTable_FieldCount(src_mt);
}
// Convert fields in descending order of field number.
while (dst_f != dst_first || src_f != src_first) {
uint32_t dst_nr =
dst_f != dst_first ? upb_MiniTableField_Number(dst_f - 1) : 0;
uint32_t src_nr =
src_f != src_first ? upb_MiniTableField_Number(src_f - 1) : 0;
if (dst_nr == src_nr) {
const upb_MiniTableField* dst_next = dst_f - 1;
const upb_MiniTableField* src_next = src_f - 1;
if (UPB_UNLIKELY(!_upb_MiniTableField_IsCompatible(src_next, dst_next))) {
upb_ErrorHandler_ThrowError(&c->err, kUpb_ErrorCode_Malformed);
}
upb_Message_ConvertField(c, dst, src, dst_next, src_next, extreg, depth);
dst_f--;
src_f--;
} else if (dst_nr > src_nr) {
dst_f--;
} else {
const upb_MiniTableField* src_next = src_f - 1;
upb_Message_EncodeFieldAsUnknown(&c->encoder, dst, src, src_next, depth,
&c->err);
src_f--;
}
}
// Convert extensions.
if (src_mt->UPB_PRIVATE(ext) != kUpb_ExtMode_NonExtendable) {
upb_Message_ConvertExtensions(c, dst, src, dst_mt, extreg, depth);
}
// Convert unknown fields.
upb_StringView data;
size_t iter = kUpb_Message_UnknownBegin;
while (upb_Message_NextUnknown(src, &data, &iter)) {
int decode_options =
upb_Decode_LimitDepth(kUpb_DecodeOption_AliasString, depth);
// Reuse d. Reset input stream.
const char* ptr = data.data;
upb_Decoder* d = &c->decoder;
upb_EpsCopyInputStream_InitWithErrorHandler(&d->input, &ptr, data.size,
d->err);
upb_Decoder_Reset(d, decode_options, dst);
_upb_Decoder_DecodeMessage(d, ptr, dst, dst_mt);
UPB_ASSERT(d->end_group == DECODE_NOGROUP);
}
}
static bool upb_Message_DoConvert(upb_Converter* c, upb_Message* dst,
const upb_Message* src,
const upb_MiniTable* dst_mt,
const upb_MiniTable* src_mt,
const upb_ExtensionRegistry* extreg) {
if (UPB_SETJMP(c->err.buf) == 0) {
upb_Message_ConvertInternal(c, dst, src, dst_mt, src_mt, extreg, 100);
return true;
}
return false;
}
const upb_Message* upb_Message_Convert(const upb_Message* src,
const upb_MiniTable* src_mt,
const upb_MiniTable* dst_mt,
const upb_ExtensionRegistry* extreg,
upb_Arena* arena) {
if (dst_mt == src_mt && extreg == NULL) return src;
upb_Message* dst = upb_Message_New(dst_mt, arena);
if (!dst) return NULL;
upb_Converter c;
upb_ErrorHandler_Init(&c.err);
// Initialize the decoder.
// Initialize decoder once, performing SwapIn.
// We use a NULL buffer initially, effectively a dummy init to set up the
// arena and error handler. Note: we pass &c.err.
upb_Decoder_Init(&c.decoder, NULL, 0, extreg, 0, arena, &c.err, NULL, 0);
// Initialize the encoder.
UPB_PRIVATE(_upb_encstate_init)(&c.encoder, &c.err.buf, &c.decoder.arena);
c.arena = &c.decoder.arena;
if (!upb_Message_DoConvert(&c, dst, src, dst_mt, src_mt, extreg)) {
dst = NULL;
}
#ifndef NDEBUG
if (dst) {
char *wire_buf, *wire_buf2;
size_t wire_size, wire_size2;
upb_Arena* tmp_arena = upb_Arena_New();
// Compare the encoded/decoded round-trip of the original message to the
// encoded/decoded round-trip of the converted message.
//
// We cannot compare the messages directly using
// UPB_ASSERT(upb_Message_IsEqual(dst, decoded_msg, dst_mt, 0)), as we
// essentially handle the non-canonical extensions differently in the
// conversion. See b/510055656 for details.
// Instead, we compare the encoded/decoded round-trip of the original
// message to the encoded/decoded round-trip of the converted message.
// They should be identical, as the non-canonical extensions will be encoded
// as unknown fields.
// Round-trip 1: encode/decode original message `src`
upb_EncodeStatus encode_status =
upb_Encode(src, src_mt, 0, tmp_arena, &wire_buf, &wire_size);
UPB_ASSERT(encode_status == kUpb_EncodeStatus_Ok);
upb_Message* decoded_msg = upb_Message_New(dst_mt, tmp_arena);
upb_DecodeStatus decode_status = upb_Decode(
wire_buf, wire_size, decoded_msg, dst_mt, extreg, 0, tmp_arena);
UPB_ASSERT(decode_status == kUpb_DecodeStatus_Ok);
// Round-trip 2: encode/decode converted message `dst`
upb_EncodeStatus encode_status2 =
upb_Encode(dst, dst_mt, 0, tmp_arena, &wire_buf2, &wire_size2);
UPB_ASSERT(encode_status2 == kUpb_EncodeStatus_Ok);
upb_Message* decoded_msg2 = upb_Message_New(dst_mt, tmp_arena);
upb_DecodeStatus decode_status2 = upb_Decode(
wire_buf2, wire_size2, decoded_msg2, dst_mt, extreg, 0, tmp_arena);
UPB_ASSERT(decode_status2 == kUpb_DecodeStatus_Ok);
// Compare both decoded messages.
UPB_ASSERT(upb_Message_IsEqual(decoded_msg, decoded_msg2, dst_mt, 0));
upb_Arena_Free(tmp_arena);
}
#endif
upb_Decoder_Destroy(&c.decoder, arena);
UPB_PRIVATE(_upb_encstate_destroy)(&c.encoder);
return dst;
}

59
upb/message/convert.h Normal file
View file

@ -0,0 +1,59 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef UPB_MESSAGE_CONVERT_H_
#define UPB_MESSAGE_CONVERT_H_
#include "upb/mem/arena.h"
#include "upb/message/message.h"
#include "upb/mini_table/extension_registry.h"
#include "upb/mini_table/message.h"
// Must be last.
#include "upb/port/def.inc"
#ifdef __cplusplus
extern "C" {
#endif
// Converts a message between two different `upb_MiniTable` types. The source
// and destination `upb_MiniTable`s must be compatible, e.g. they are both
// derived from the same proto definition (e.g., via tree-shaking) or subsets of
// some message proto.
//
// It is equivalent to encoding the source message and then decoding it
// using the destination `upb_MiniTable`, but is generally faster and uses less
// memory.
//
// If the destination message, or any of its submessages, uses the same
// minitable as the matching part of the source (determined by pointer
// identity), the destination will alias the source's memory (shallow copy)
// instead of performing a deep copy. Strings and unknown fields are aliased
// from the input message.
//
// Fields present in the source but not the destination will be encoded and
// added to the destination's unknown fields (or extensions, if the extension
// registry allows it).
//
// Returns a new message on success, or NULL on failure. This function may
// return NULL even for valid inputs, if it encounters a case it does not
// support. If the caller wishes to handle all cases, they should detect NULL
// and fallback to serializing the source message and then decoding it using
// the destination `upb_MiniTable`.
const upb_Message* upb_Message_Convert(const upb_Message* src,
const upb_MiniTable* src_mt,
const upb_MiniTable* dst_mt,
const upb_ExtensionRegistry* extreg,
upb_Arena* arena);
#ifdef __cplusplus
} // extern "C"
#endif
#include "upb/port/undef.inc"
#endif // UPB_MESSAGE_CONVERT_H_

View file

@ -0,0 +1,343 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#include <gtest/gtest.h>
#include "testing/fuzzing/fuzztest.h"
#include "upb/mem/arena.h"
#include "upb/message/compare.h"
#include "upb/message/convert.h"
#include "upb/message/message.h"
#include "upb/mini_table/extension_registry.h"
#include "upb/mini_table/field.h"
#include "upb/mini_table/internal/field.h"
#include "upb/mini_table/internal/message.h"
#include "upb/mini_table/internal/sub.h"
#include "upb/mini_table/message.h"
#include "upb/test/fuzz_util.h"
#include "upb/wire/decode.h"
#include "upb/wire/encode.h"
// Must be last.
#include "upb/port/def.inc"
namespace upb {
namespace {
const upb_MiniTable* SubsetMiniTable(const upb_MiniTable* src, uint64_t mask,
upb_Arena* arena);
const upb_MiniTable* SubsetMiniTable(const upb_MiniTable* src, uint64_t mask,
upb_Arena* arena) {
std::vector<upb_MiniTableField> new_fields;
std::vector<upb_MiniTableSub> new_subs;
int field_count = upb_MiniTable_FieldCount(src);
for (int i = 0; i < field_count; ++i) {
const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(src, i);
if (mask & (1ULL << (i % 64))) {
upb_MiniTableField new_f = *f;
if (f->UPB_PRIVATE(submsg_ofs) != kUpb_NoSub) {
new_subs.push_back(
*UPB_PTR_AT(f, f->UPB_PRIVATE(submsg_ofs) * kUpb_SubmsgOffsetBytes,
const upb_MiniTableSub));
}
new_fields.push_back(new_f);
}
}
size_t mt_size = sizeof(upb_MiniTable);
size_t fields_bytes = new_fields.size() * sizeof(upb_MiniTableField);
size_t subs_bytes = new_subs.size() * sizeof(upb_MiniTableSub);
size_t mt_padded_size = UPB_ALIGN_UP(mt_size, 8);
size_t fields_padded_size = UPB_ALIGN_UP(fields_bytes, 8);
size_t total_size = mt_padded_size + fields_padded_size + subs_bytes;
upb_MiniTable* new_mt = (upb_MiniTable*)upb_Arena_Malloc(arena, total_size);
upb_MiniTableField* fields_ptr =
UPB_PTR_AT(new_mt, mt_padded_size, upb_MiniTableField);
upb_MiniTableSub* subs_ptr =
UPB_PTR_AT(fields_ptr, fields_padded_size, upb_MiniTableSub);
std::memcpy(new_mt, src, mt_size);
if (!new_fields.empty()) {
std::memcpy(fields_ptr, new_fields.data(), fields_bytes);
}
if (!new_subs.empty()) {
std::memcpy(subs_ptr, new_subs.data(), subs_bytes);
}
int sub_idx = 0;
for (size_t i = 0; i < new_fields.size(); ++i) {
upb_MiniTableField* f = &fields_ptr[i];
if (f->UPB_PRIVATE(submsg_ofs) != kUpb_NoSub) {
uintptr_t f_addr = (uintptr_t)f;
uintptr_t subs_addr = (uintptr_t)&subs_ptr[sub_idx++];
size_t diff = subs_addr - f_addr;
f->UPB_PRIVATE(submsg_ofs) = (uint16_t)(diff / kUpb_SubmsgOffsetBytes);
}
}
new_mt->UPB_ONLYBITS(fields) = fields_ptr;
new_mt->UPB_ONLYBITS(field_count) = (uint16_t)new_fields.size();
new_mt->UPB_PRIVATE(dense_below) = 0;
new_mt->UPB_PRIVATE(table_mask) = -1;
return new_mt;
}
void ConvertFuzz(const upb::fuzz::MiniTableFuzzInput& input, uint64_t mask1,
uint64_t mask2, std::string proto_payload,
uint32_t decode_options, uint32_t encode_options) {
upb_Arena* arena = upb_Arena_New();
upb_ExtensionRegistry* exts;
const upb_MiniTable* original_mt =
upb::fuzz::BuildMiniTable(input, &exts, arena);
if (!original_mt) {
upb_Arena_Free(arena);
return;
}
const upb_MiniTable* src_mt = SubsetMiniTable(original_mt, mask1, arena);
const upb_MiniTable* dst_mt = SubsetMiniTable(original_mt, mask2, arena);
decode_options = upb_Decode_LimitDepth(decode_options, 80);
encode_options = upb_Encode_LimitDepth(encode_options, 80);
// We don't want to skip unknown fields or check required fields, as these
// will cause the fuzz test to fail or exit early in ways that aren't
// interesting.
encode_options &=
~(kUpb_EncodeOption_SkipUnknown | kUpb_EncodeOption_CheckRequired);
upb_Message* msg_orig = upb_Message_New(original_mt, arena);
upb_DecodeStatus status =
upb_Decode(proto_payload.data(), proto_payload.size(), msg_orig,
original_mt, exts, decode_options, arena);
if (status != kUpb_DecodeStatus_Ok) {
upb_Arena_Free(arena);
return;
}
upb_Message* msg_src = upb_Message_New(src_mt, arena);
status = upb_Decode(proto_payload.data(), proto_payload.size(), msg_src,
src_mt, exts, decode_options, arena);
if (status != kUpb_DecodeStatus_Ok) {
upb_Arena_Free(arena);
return;
}
const upb_Message* msg_dst =
upb_Message_Convert(msg_src, src_mt, dst_mt, nullptr, arena);
if (!msg_dst) {
upb_Arena_Free(arena);
return;
}
size_t size;
char* bytes;
upb_EncodeStatus enc_status =
upb_Encode(msg_dst, dst_mt, encode_options, arena, &bytes, &size);
if (enc_status != kUpb_EncodeStatus_Ok) {
upb_Arena_Free(arena);
return;
}
upb_Message* msg_final = upb_Message_New(original_mt, arena);
status = upb_Decode(bytes, size, msg_final, original_mt, exts, decode_options,
arena);
if (status != kUpb_DecodeStatus_Ok) {
upb_Arena_Free(arena);
return;
}
bool equal = upb_Message_IsEqual(msg_final, msg_orig, original_mt,
kUpb_CompareOption_IncludeUnknownFields);
if (!equal) {
abort();
}
upb_Arena_Free(arena);
}
FUZZ_TEST(ConvertFuzz, ConvertFuzz);
TEST(ConvertFuzz, Convert_IdenticalMinitables_ShallowCopy) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{
{"\331", ""}, {"\257"}, "\351\210", {2147483645}},
4867317803475403639ULL, 4867317803475403639ULL, "@B", 3220282077ULL,
4189253947ULL);
}
TEST(ConvertFuzz, Convert_DifferentSubsets_DroppedFields_Complex) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{
{"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", ""},
{"\306", "", "\331", ""},
"\314Q\345\322\024\321\017\021\016\016\016",
{1, 1, 0, 4294967295, 4294967295,
1, 4294967295, 4294967295, 4294967295, 4294967295,
4294967295, 4294967295, 4294967295, 4294967295, 4294967295,
1, 3135226496, 3135226496, 3135226496, 0,
3708883408}},
14162464183153632842ULL, 2939036597827910531ULL,
"=\032\253\342K\221MQ\nj\n\304\361\364\304", 1620786928, 2077918455);
}
TEST(ConvertFuzz, Convert_DifferentSubsets_DroppedFields_Simple) {
ConvertFuzz(upb::fuzz::MiniTableFuzzInput{{"", "", "", "", "", "", "", "", "",
"", "", "", "", "", ""},
{""},
"",
{4294967295, 4294967295, 0}},
4453300303650383170ULL, 13549612121351043620ULL, "]]k\031\320",
1804506072, 3299701550);
}
TEST(ConvertFuzz,
Convert_DifferentSubsets_DroppedFields_Regression_89982ef4cfe52331) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{
{"", "DD", "z", "", "", "\200"}, {"\031", "\031", "\031"}, "]]", {}},
7595264386850118164ULL, 1703605996941841092ULL, "\300D\315\316\372D",
3067512230, 2237854510);
}
TEST(ConvertFuzz, ManualUnknownFieldRepro) {
ConvertFuzz(upb::fuzz::MiniTableFuzzInput{{"I", "a"}}, ~0ULL, 0ULL,
"\010\000", 0, 0);
}
TEST(ConvertFuzz, Regression_FuzzerFinding_UnknownFields) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{
{"", "", ""}, {"", "\211", "", "{", "\232", "2"}, "", {4294967295}},
5205171011975038739ULL, 18014293590545137538ULL, "E\261k\333\334",
590856108, 53173774);
}
TEST(ConvertFuzz, RepeatedFieldConfusion) {
ConvertFuzz(upb::fuzz::MiniTableFuzzInput{{"\t", "\352", "R", "$\214\214\214",
"\004", "$F", "\244", "$\374P"},
{""},
"{GGG",
{4294967295}},
3117542645911838959, 9223372036854775807, "", 2147221503, 16);
}
TEST(ConvertFuzz, ConvertFuzzRegression) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{{""}, {"\325\242"}, "", {2147483647}},
18446744073709551615u, 10490713252739160816u, " u", 2, 4294967231);
}
TEST(ConvertFuzz, ConvertFuzzRegression_Crash_ConvertInternal_2026_04_07) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{
{""},
{"", ">>", "", ""},
"\357\357\357\357\357*"
"\357\357\357\357\357\357\357\357\357\357\357\357\357\357\357\357\357"
"\357\225\225\225\357\357\357\357\357\357\357\357\357\357\357\357\357"
"\357\357\357\357\357\357\357\357\357",
{4294967295, 4294967295, 4294967295, 4294967295, 4294967295,
4294967295, 4294967295, 4294967295, 1}},
12411566311597166710ULL, 8799022017001952335ULL, "%%%%%%%%%%%%%%%%%ZZ%",
2815879784ULL, 3243839465ULL);
}
TEST(ConvertFuzz, Convert_Fuzz_Crash_Regression) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{
{"", "", ""}, {"", ""}, "\207\254", {4033684729, 4294967294}},
7437277671727957814ULL, 9800553862402446025ULL,
"\220Y\203\225\225\366\337\337\004", 3062476814ULL, 4041248646ULL);
}
TEST(ConvertFuzz, ConvertFuzzRegression2) {
ConvertFuzz(upb::fuzz::MiniTableFuzzInput{{"", ""}, {""}, "", {1903095162}},
8810965477460052779, 18446744073709551615u, "\030\030", 114210880,
8190);
}
TEST(ConvertFuzz, ConvertFuzzRegression3) {
ConvertFuzz(upb::fuzz::MiniTableFuzzInput{{""}, {}, "\373", {}}, 1,
9223372036854775807, "uz\010\006\006", 4294967290, 2147483647);
}
TEST(ConvertFuzz, ConvertFuzzRegression4) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{
{"$", "C", ""},
{"r"},
"\270N\270[",
{3621539714, 3621539714, 3621539714, 3621539714, 3621539714,
3621539714, 3621539714, 3621539714, 3621539714, 3621539714,
3621539714, 3621539714, 3621539714, 3621539714, 3621539714,
3621539714, 3621539714, 3621539714, 3621539714, 3621539714,
3621539714, 3621539714, 3621539714, 3621539714, 3621539714,
3621539714, 3621539714, 3621539714, 3621539714, 3621539714,
3621539714, 3621539714, 3621539714, 3621539714, 3621539714,
3621539714, 3621539714, 3621539714, 3621539714, 3621539714,
3621539714, 3621539714, 3621539714, 3621539714}},
13429813313754688149ULL, 6819704094736752274ULL, "\255\032\262\337))",
1611082566, 466142871);
}
TEST(ConvertFuzz, ConvertFuzzRegression5) {
ConvertFuzz(upb::fuzz::MiniTableFuzzInput{{""}, {"\362", "y"}, "\252", {}},
8728619288361297649ULL, 13997967148621545864ULL,
"%\311\311\311\303", 3383556142, 3604956055);
}
TEST(ConvertFuzz, ConvertFuzzRegression6) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{
{"", "", "\341", "\341", "\341", "$", "", "", "\022", "\022", "", "",
""},
{"\374", "\204"},
"n\251PXq",
{1, 1, 1, 1, 1,
1, 1, 1413050348, 1, 1,
1, 1, 1, 0, 0,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 4,
4294967295, 4294967295, 4294967295, 4294967295, 4294967295,
4294967295, 4294967295, 4294967295, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 3028396088, 1,
1, 1, 1, 1, 4294967295,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 0,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1}},
304186569338573724ULL, 6555165345294515829ULL, "8W", 3970284081,
1611359599);
}
TEST(ConvertFuzz, ConvertFuzzEncodeRegression) {
ConvertFuzz(
upb::fuzz::MiniTableFuzzInput{{"$$$$$$$$$$$$$$$$$$", "", "", "", "", ""},
{"", ""},
"D",
{2462394141, 2462394141, 2462394145}},
555217012043469213, 13507447059222749214u,
"pm\t\t\t\t\t\t\t\t\t\t\t\005o\t\t\t\t\t\trr\375\375\375r\251r", 32766,
16);
}
} // namespace
} // namespace upb

1178
upb/message/convert_test.cc Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,148 @@
syntax = "proto2";
package upb.test.convert;
option java_multiple_files = true;
option java_outer_classname = "ConvertTest";
message MessageWithExtension {
extensions 1000 to max;
}
extend MessageWithExtension {
optional int32 ext_field_int32 = 1000;
repeated MessageWithInt32 ext_field_repeated_msg = 1001;
}
message MessageWithMapMessage {
map<int32, MessageWithInt32> map_msg = 1;
}
message MessageWithMapMessageClone {
map<int32, MessageWithInt32Clone> map_msg = 1;
}
message MessageWithKnown {
optional int32 known_field_int32 = 1000;
}
message MessageWithKnownInt64 {
optional int64 known_field_int64 = 1000;
}
message MessageWithKnownRepeatedMsg {
repeated MessageWithInt32 known_repeated_msg = 1001;
}
message MessageWithInt32 {
optional int32 f1 = 1;
}
message MessageWithInt32Clone {
optional int32 f1 = 1;
}
extend MessageWithExtension {
optional MessageWithInt32 ext_field_msg = 1002;
}
message MessageWithKnownMsg {
optional MessageWithInt32 known_msg = 1002;
}
message MessageWithKnownMsgClone {
optional MessageWithInt32Clone known_msg = 1002;
}
message MessageWithRepeatedMsg {
repeated MessageWithInt32 msgs = 1;
}
message MessageWithRepeatedMsgClone {
repeated MessageWithInt32Clone msgs = 1;
}
message MessageWithMsg {
optional MessageWithInt32 msg = 1;
}
message MessageWithMsgClone {
optional MessageWithInt32Clone msg = 1;
}
message MessageWithKnownRepeatedMsgClone {
repeated MessageWithInt32Clone known_repeated_msg = 1001;
}
message MessageWithInt64 {
optional int64 f1 = 1;
}
message MessageWithString {
optional string f1 = 1;
}
message MessageWithRepeatedInt32 {
repeated int32 r = 1;
}
message MessageWithRepeatedInt64 {
repeated int64 r = 1;
}
message MessageWithRepeatedString {
repeated string r = 1;
}
message MessageWithMapInt32Int32 {
map<int32, int32> m = 1;
}
message MessageWithMapInt32Int32Clone {
map<int32, int32> m = 1;
}
message MessageWithMapInt32Int64 {
map<int32, int64> m = 1;
}
message AnotherMessageWithExtension {
extensions 1000 to max;
}
extend AnotherMessageWithExtension {
optional int32 another_ext_field_int32 = 1000;
}
message SrcWithOneof {
oneof my_oneof {
int32 oneof_int32 = 1;
string oneof_string = 2;
}
}
message Proto2EnumMessage {
enum NestedEnum {
FOO = 0;
BAR = 1;
BAZ = 2;
NEG = -1;
}
optional NestedEnum optional_nested_enum = 21;
repeated NestedEnum repeated_nested_enum = 51;
map<string, NestedEnum> map_string_nested_enum = 73;
}
extend MessageWithExtension {
optional Proto2EnumMessage.NestedEnum ext_enum = 2000;
repeated Proto2EnumMessage.NestedEnum ext_repeated_enum = 2001;
}
message MessageWithKnownEnum {
optional Proto2EnumMessage.NestedEnum ext_enum = 2000;
repeated Proto2EnumMessage.NestedEnum ext_repeated_enum = 2001;
}
message MessageWithMapAt1000 {
map<int32, int32> m = 1000;
}