Added a new dynamic tree shaking model to upb, with the intention of removing the old model once YouTube has migrated.

The `kUpb_DecodeOption_ExperimentalAllowUnlinked` flag to the decoder will enable the new behavior.  When that flag is not passed, tree shaking with the old model will still be possible.

"Dynamic tree shaking" in upb is a feature that allows messages to be parsed even if the MiniTables have not been fully linked.  Unlinked sub-message fields can be parsed by preserving their data in the unknown fields.  If the application later discovers that the message field is actually needed, the MiniTable can be patched to properly link that field, and existing message instances can "promote" the data from the unknown fields to an actual message of the correct type.

Before this change, dynamic tree shaking stored unparsed message data in the unknown fields of the *parent*.  In effect, we were treating the field as if it did not exist at all.  This meant that parsing an unlinked field did not affect the hasbits or oneof cases of the parent, nor did it create a `upb_Array` or `upb_Map` for array/map fields.  Only when a message was linked and promoted did any of these things occur.

While this model had some amount of conceptual simplicity, it caused significant problems with oneofs.  When multiple fields inside a single oneof are parsed from the wire, order matters, because later oneof fields must overwrite earlier ones.  Dynamic tree shaking can mean that some fields in a oneof are linked while others are not.  It is essential that we preserve this ordering semantic even when dynamic tree shaking is being used, but it is difficult to do if the oneof's data can be split between linked fields (which have been reified into parsed field data) and unlinked fields (whose data lives in the unknown fields of the parent).

To solve this problem, this CL changes the representation for unlinked fields.  Instead of being placed in the parent's unknown fields, we create an actual message instance for each unlinked message we parse, but we use a placeholder "empty message" MiniTable as the message's type.  All of the message's data will therefore be placed into the "empty message's" unknown fields.  But unlike before, this "empty message" is actually present according to the hasbits, oneof case, and `upb_Array`/`upb_Map` of the parent.  This means that all of the oneof presence logic works as normal.

Since the MiniTable can be patched at any time, we need a bit in the message instance itself to signal whether a pointer to a sub-message is an "empty message" or not.  When dynamic tree shaking is in use, all users must be capable of recognizing an empty message and acting accordingly (promoting, etc) even if the MiniTable itself says that the field is linked.

Because dynamic tree shaking imposes this extra requirement on users, we require that users pass an extra option to the decoder to allow parsing of unlinked sub-messages.  Many existing users of upb (Ruby, PHP, Python, etc) will always have fully-linked MiniTables, so there is no reason for them to add extra logic to handle empty messages.  By omitting the `kUpb_DecodeOption_ExperimentalAllowUnlinked` option, they will be relieved of the duty to check the tagged pointer that would indicate an empty, unlinked message.

For existing users of dynamic tree shaking, there are three main changes:

1. The APIs in message/promote.h have changed, and users will need to update to the new interfaces.

2. The model for maps has changed slightly.  Before, we required that map entries always had their values linked; for dynamic tree shaking to apply to maps, we required that the *entry* was left unlinked, not the entry's value.  In the new model, that is reversed: map entries must always be linked, but a map entry's value can be unlinked.

3. The presence model for unlinked fields has changed.  Unlinked fields will now register as "present" from the perspective of hasbits, oneof cases, and array/map entries.  Users must test the tagged pointer to know if a message is of the correct, linked type or whether it is a placeholder "empty" message.  There is a new function `upb_Message_GetTaggedMessagePtr()`, as well as a new accessor `upb_MessageValue.tagged_msg_val` that can be used to read and test the tagged pointer directly.

PiperOrigin-RevId: 535288031
pull/13171/head
Joshua Haberman 2 years ago committed by Copybara-Service
parent 14bad4a5bf
commit a0f520dc75
  1. 18
      BUILD
  2. 7
      upb/collections/map.c
  3. 5
      upb/collections/map.h
  4. 8
      upb/collections/message_value.h
  5. 5
      upb/hash/common.c
  6. 1
      upb/hash/common.h
  7. 1
      upb/hash/str_table.h
  8. 40
      upb/message/accessors.h
  9. 13
      upb/message/accessors_internal.h
  10. 26
      upb/message/copy.c
  11. 105
      upb/message/promote.c
  12. 49
      upb/message/promote.h
  13. 435
      upb/message/promote_test.cc
  14. 5
      upb/mini_table/common.h
  15. 5
      upb/mini_table/decode.c
  16. 6
      upb/mini_table/encode_test.cc
  17. 39
      upb/mini_table/message_internal.c
  18. 3
      upb/mini_table/message_internal.h
  19. 56
      upb/mini_table/types.h
  20. 2
      upb/test/test.proto
  21. 93
      upb/wire/decode.c
  22. 40
      upb/wire/decode.h
  23. 33
      upb/wire/encode.c

18
BUILD

@ -210,6 +210,7 @@ cc_library(
"upb/mini_table/decode.c",
"upb/mini_table/encode.c",
"upb/mini_table/extension_registry.c",
"upb/mini_table/message_internal.c",
],
hdrs = [
"upb/mini_table/common.h",
@ -271,6 +272,21 @@ cc_library(
],
)
cc_library(
name = "message_accessors_internal",
hdrs = [
"upb/message/accessors_internal.h",
],
copts = UPB_DEFAULT_COPTS,
visibility = ["//:friends"],
deps = [
":collections_internal",
":message_internal",
":mini_table_internal",
":port",
],
)
cc_library(
name = "message_accessors",
srcs = [
@ -394,6 +410,7 @@ cc_test(
deps = [
":collections",
":message_accessors",
":message_copy",
":message_promote",
":mini_table_internal",
":port",
@ -978,6 +995,7 @@ cc_library(
":eps_copy_input_stream",
":hash",
":mem_internal",
":message_accessors_internal",
":message_internal",
":mini_table_internal",
":port",

@ -90,6 +90,13 @@ bool upb_Map_Next(const upb_Map* map, upb_MessageValue* key,
return ok;
}
UPB_API void upb_Map_SetEntryValue(upb_Map* map, size_t iter,
upb_MessageValue val) {
upb_value v;
_upb_map_tovalue(&val, map->val_size, &v, NULL);
upb_strtable_setentryvalue(&map->table, iter, v);
}
bool upb_MapIterator_Next(const upb_Map* map, size_t* iter) {
return _upb_map_next(map, iter);
}

@ -103,6 +103,11 @@ UPB_INLINE bool upb_Map_Delete2(upb_Map* map, upb_MessageValue key,
UPB_API bool upb_Map_Next(const upb_Map* map, upb_MessageValue* key,
upb_MessageValue* val, size_t* iter);
// Sets the value for the entry pointed to by iter.
// WARNING: this does not currently work for string values!
UPB_API void upb_Map_SetEntryValue(upb_Map* map, size_t iter,
upb_MessageValue val);
// DEPRECATED iterator, slated for removal.
/* Map iteration:

@ -40,6 +40,8 @@
typedef struct upb_Array upb_Array;
typedef struct upb_Map upb_Map;
typedef uintptr_t upb_TaggedMessagePtr;
typedef union {
bool bool_val;
float float_val;
@ -52,6 +54,12 @@ typedef union {
const upb_Map* map_val;
const upb_Message* msg_val;
upb_StringView str_val;
// EXPERIMENTAL: A tagged upb_Message*. Users must use this instead of
// msg_val if unlinked sub-messages may possibly be in use. See the
// documentation in kUpb_DecodeOption_ExperimentalAllowUnlinked for more
// information.
upb_TaggedMessagePtr tagged_msg_val;
} upb_MessageValue;
typedef union {

@ -866,3 +866,8 @@ void upb_strtable_removeiter(upb_strtable* t, intptr_t* iter) {
ent->key = 0;
ent->next = NULL;
}
void upb_strtable_setentryvalue(upb_strtable* t, intptr_t iter, upb_value v) {
upb_tabent* ent = &t->t.entries[iter];
ent->val.val = v.val;
}

@ -98,6 +98,7 @@ FUNCS(uint32, uint32, uint32_t, uint32_t, UPB_CTYPE_UINT32)
FUNCS(uint64, uint64, uint64_t, uint64_t, UPB_CTYPE_UINT64)
FUNCS(bool, _bool, bool, bool, UPB_CTYPE_BOOL)
FUNCS(cstr, cstr, char*, uintptr_t, UPB_CTYPE_CSTR)
FUNCS(uintptr, uptr, uintptr_t, uintptr_t, UPB_CTYPE_UPTR)
FUNCS(ptr, ptr, void*, uintptr_t, UPB_CTYPE_PTR)
FUNCS(constptr, constptr, const void*, uintptr_t, UPB_CTYPE_CONSTPTR)

@ -100,6 +100,7 @@ bool upb_strtable_resize(upb_strtable* t, size_t size_lg2, upb_Arena* a);
bool upb_strtable_next2(const upb_strtable* t, upb_StringView* key,
upb_value* val, intptr_t* iter);
void upb_strtable_removeiter(upb_strtable* t, intptr_t* iter);
void upb_strtable_setentryvalue(upb_strtable* t, intptr_t iter, upb_value v);
/* DEPRECATED iterators, slated for removal.
*

@ -254,22 +254,32 @@ UPB_API_INLINE bool upb_Message_SetString(upb_Message* msg,
return _upb_Message_SetField(msg, field, &value, a);
}
UPB_API_INLINE const upb_Message* upb_Message_GetMessage(
UPB_API_INLINE upb_TaggedMessagePtr upb_Message_GetTaggedMessagePtr(
const upb_Message* msg, const upb_MiniTableField* field,
upb_Message* default_val) {
UPB_ASSUME(upb_MiniTableField_CType(field) == kUpb_CType_Message);
UPB_ASSUME(_upb_MiniTableField_GetRep(field) ==
UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte));
UPB_ASSUME(!upb_IsRepeatedOrMap(field));
upb_Message* ret;
_upb_Message_GetNonExtensionField(msg, field, &default_val, &ret);
return ret;
upb_TaggedMessagePtr tagged;
_upb_Message_GetNonExtensionField(msg, field, &default_val, &tagged);
return tagged;
}
UPB_API_INLINE void upb_Message_SetMessage(upb_Message* msg,
const upb_MiniTable* mini_table,
const upb_MiniTableField* field,
upb_Message* sub_message) {
UPB_API_INLINE const upb_Message* upb_Message_GetMessage(
const upb_Message* msg, const upb_MiniTableField* field,
upb_Message* default_val) {
upb_TaggedMessagePtr tagged =
upb_Message_GetTaggedMessagePtr(msg, field, default_val);
return upb_TaggedMessagePtr_GetNonEmptyMessage(tagged);
}
// For internal use only; users cannot set tagged messages because only the
// parser and the message copier are allowed to directly create an empty
// message.
UPB_API_INLINE void _upb_Message_SetTaggedMessagePtr(
upb_Message* msg, const upb_MiniTable* mini_table,
const upb_MiniTableField* field, upb_TaggedMessagePtr sub_message) {
UPB_ASSUME(upb_MiniTableField_CType(field) == kUpb_CType_Message);
UPB_ASSUME(_upb_MiniTableField_GetRep(field) ==
UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte));
@ -278,6 +288,14 @@ UPB_API_INLINE void upb_Message_SetMessage(upb_Message* msg,
_upb_Message_SetNonExtensionField(msg, field, &sub_message);
}
UPB_API_INLINE void upb_Message_SetMessage(upb_Message* msg,
const upb_MiniTable* mini_table,
const upb_MiniTableField* field,
upb_Message* sub_message) {
_upb_Message_SetTaggedMessagePtr(
msg, mini_table, field, _upb_TaggedMessagePtr_Pack(sub_message, false));
}
UPB_API_INLINE upb_Message* upb_Message_GetOrCreateMutableMessage(
upb_Message* msg, const upb_MiniTable* mini_table,
const upb_MiniTableField* field, upb_Arena* arena) {
@ -336,12 +354,18 @@ UPB_API_INLINE void* upb_Message_ResizeArrayUninitialized(
UPB_API_INLINE const upb_Map* upb_Message_GetMap(
const upb_Message* msg, const upb_MiniTableField* field) {
_upb_MiniTableField_CheckIsMap(field);
_upb_Message_AssertMapIsUntagged(msg, field);
upb_Map* ret;
const upb_Map* default_val = NULL;
_upb_Message_GetNonExtensionField(msg, field, &default_val, &ret);
return ret;
}
UPB_API_INLINE upb_Map* upb_Message_GetMutableMap(
upb_Message* msg, const upb_MiniTableField* field) {
return (upb_Map*)upb_Message_GetMap(msg, field);
}
UPB_API_INLINE upb_Map* upb_Message_GetOrCreateMutableMap(
upb_Message* msg, const upb_MiniTable* map_entry_mini_table,
const upb_MiniTableField* field, upb_Arena* arena) {

@ -28,7 +28,6 @@
#ifndef UPB_MESSAGE_ACCESSORS_INTERNAL_H_
#define UPB_MESSAGE_ACCESSORS_INTERNAL_H_
#include "upb/collections/array.h"
#include "upb/collections/map_internal.h"
#include "upb/message/extension_internal.h"
#include "upb/message/internal.h"
@ -296,10 +295,22 @@ UPB_INLINE void _upb_Message_ClearNonExtensionField(
field);
}
UPB_INLINE void _upb_Message_AssertMapIsUntagged(
const upb_Message* msg, const upb_MiniTableField* field) {
_upb_MiniTableField_CheckIsMap(field);
#ifndef NDEBUG
upb_TaggedMessagePtr default_val = 0;
upb_TaggedMessagePtr tagged;
_upb_Message_GetNonExtensionField(msg, field, &default_val, &tagged);
UPB_ASSERT(!upb_TaggedMessagePtr_IsEmpty(tagged));
#endif
}
UPB_INLINE upb_Map* _upb_Message_GetOrCreateMutableMap(
upb_Message* msg, const upb_MiniTableField* field, size_t key_size,
size_t val_size, upb_Arena* arena) {
_upb_MiniTableField_CheckIsMap(field);
_upb_Message_AssertMapIsUntagged(msg, field);
upb_Map* map = NULL;
upb_Map* default_map_value = NULL;
_upb_Message_GetNonExtensionField(msg, field, &default_map_value, &map);

@ -77,11 +77,14 @@ static bool upb_Clone_MessageValue(void* value, upb_CType value_type,
return true;
} break;
case kUpb_CType_Message: {
UPB_ASSERT(sub);
const upb_Message* source = *(upb_Message**)value;
const upb_TaggedMessagePtr source = *(upb_TaggedMessagePtr*)value;
bool is_empty = upb_TaggedMessagePtr_IsEmpty(source);
if (is_empty) sub = &_kUpb_MiniTable_Empty;
UPB_ASSERT(source);
upb_Message* clone = upb_Message_DeepClone(source, sub, arena);
*(upb_Message**)value = clone;
upb_Message* clone = upb_Message_DeepClone(
_upb_TaggedMessagePtr_GetMessage(source), sub, arena);
*(upb_TaggedMessagePtr*)value =
_upb_TaggedMessagePtr_Pack(clone, is_empty);
return clone != NULL;
} break;
}
@ -198,17 +201,26 @@ upb_Message* _upb_Message_Copy(upb_Message* dst, const upb_Message* src,
if (!upb_IsRepeatedOrMap(field)) {
switch (upb_MiniTableField_CType(field)) {
case kUpb_CType_Message: {
upb_TaggedMessagePtr tagged =
upb_Message_GetTaggedMessagePtr(src, field, NULL);
const upb_Message* sub_message =
upb_Message_GetMessage(src, field, NULL);
_upb_TaggedMessagePtr_GetMessage(tagged);
if (sub_message != NULL) {
// If the message is currently in an unlinked, "empty" state we keep
// it that way, because we don't want to deal with decode options,
// decode status, or possible parse failure here.
bool is_empty = upb_TaggedMessagePtr_IsEmpty(tagged);
const upb_MiniTable* sub_message_table =
upb_MiniTable_GetSubMessageTable(mini_table, field);
is_empty ? &_kUpb_MiniTable_Empty
: upb_MiniTable_GetSubMessageTable(mini_table, field);
upb_Message* dst_sub_message =
upb_Message_DeepClone(sub_message, sub_message_table, arena);
if (dst_sub_message == NULL) {
return NULL;
}
upb_Message_SetMessage(dst, mini_table, field, dst_sub_message);
_upb_Message_SetTaggedMessagePtr(
dst, mini_table, field,
_upb_TaggedMessagePtr_Pack(dst_sub_message, is_empty));
}
} break;
case kUpb_CType_String:

@ -121,37 +121,6 @@ upb_GetExtension_Status upb_MiniTable_GetOrPromoteExtension(
return kUpb_GetExtension_Ok;
}
upb_GetExtensionAsBytes_Status upb_MiniTable_GetExtensionAsBytes(
const upb_Message* msg, const upb_MiniTableExtension* ext_table,
int encode_options, upb_Arena* arena, const char** extension_data,
size_t* len) {
const upb_Message_Extension* msg_ext = _upb_Message_Getext(msg, ext_table);
UPB_ASSERT(upb_MiniTableField_CType(&ext_table->field) == kUpb_CType_Message);
if (msg_ext) {
upb_EncodeStatus status =
upb_Encode(msg_ext->data.ptr, msg_ext->ext->sub.submsg, encode_options,
arena, (char**)extension_data, len);
if (status != kUpb_EncodeStatus_Ok) {
return kUpb_GetExtensionAsBytes_EncodeError;
}
return kUpb_GetExtensionAsBytes_Ok;
}
int field_number = ext_table->field.number;
upb_FindUnknownRet result = upb_MiniTable_FindUnknown(
msg, field_number, upb_DecodeOptions_GetMaxDepth(encode_options));
if (result.status != kUpb_FindUnknown_Ok) {
return kUpb_GetExtensionAsBytes_NotPresent;
}
const char* data = result.ptr;
uint32_t tag;
uint64_t message_len = 0;
data = upb_WireReader_ReadTag(data, &tag);
data = upb_WireReader_ReadVarint(data, &message_len);
*extension_data = data;
*len = message_len;
return kUpb_GetExtensionAsBytes_Ok;
}
static upb_FindUnknownRet upb_FindUnknownRet_ParseError(void) {
return (upb_FindUnknownRet){.status = kUpb_FindUnknown_ParseError};
}
@ -190,6 +159,80 @@ upb_FindUnknownRet upb_MiniTable_FindUnknown(const upb_Message* msg,
return ret;
}
static upb_DecodeStatus upb_Message_PromoteOne(upb_TaggedMessagePtr* tagged,
const upb_MiniTable* mini_table,
int decode_options,
upb_Arena* arena) {
upb_Message* empty = _upb_TaggedMessagePtr_GetEmptyMessage(*tagged);
size_t unknown_size;
const char* unknown_data = upb_Message_GetUnknown(empty, &unknown_size);
upb_Message* promoted = upb_Message_New(mini_table, arena);
if (!promoted) return kUpb_DecodeStatus_OutOfMemory;
upb_DecodeStatus status = upb_Decode(unknown_data, unknown_size, promoted,
mini_table, NULL, decode_options, arena);
if (status == kUpb_DecodeStatus_Ok) {
*tagged = _upb_TaggedMessagePtr_Pack(promoted, false);
}
return status;
}
upb_DecodeStatus upb_Message_PromoteMessage(upb_Message* parent,
const upb_MiniTable* mini_table,
const upb_MiniTableField* field,
int decode_options,
upb_Arena* arena,
upb_Message** promoted) {
const upb_MiniTable* sub_table =
upb_MiniTable_GetSubMessageTable(mini_table, field);
UPB_ASSERT(sub_table);
upb_TaggedMessagePtr tagged =
upb_Message_GetTaggedMessagePtr(parent, field, NULL);
upb_DecodeStatus ret =
upb_Message_PromoteOne(&tagged, sub_table, decode_options, arena);
if (ret == kUpb_DecodeStatus_Ok) {
*promoted = upb_TaggedMessagePtr_GetNonEmptyMessage(tagged);
upb_Message_SetMessage(parent, mini_table, field, *promoted);
}
return ret;
}
upb_DecodeStatus upb_Array_PromoteMessages(upb_Array* arr,
const upb_MiniTable* mini_table,
int decode_options,
upb_Arena* arena) {
void** data = _upb_array_ptr(arr);
size_t size = arr->size;
for (size_t i = 0; i < size; i++) {
upb_TaggedMessagePtr tagged;
memcpy(&tagged, &data[i], sizeof(tagged));
if (!upb_TaggedMessagePtr_IsEmpty(tagged)) continue;
upb_DecodeStatus status =
upb_Message_PromoteOne(&tagged, mini_table, decode_options, arena);
if (status != kUpb_DecodeStatus_Ok) return status;
memcpy(&data[i], &tagged, sizeof(tagged));
}
return kUpb_DecodeStatus_Ok;
}
upb_DecodeStatus upb_Map_PromoteMessages(upb_Map* map,
const upb_MiniTable* mini_table,
int decode_options, upb_Arena* arena) {
size_t iter = kUpb_Map_Begin;
upb_MessageValue key, val;
while (upb_Map_Next(map, &key, &val, &iter)) {
if (!upb_TaggedMessagePtr_IsEmpty(val.tagged_msg_val)) continue;
upb_DecodeStatus status = upb_Message_PromoteOne(
&val.tagged_msg_val, mini_table, decode_options, arena);
if (status != kUpb_DecodeStatus_Ok) return status;
upb_Map_SetEntryValue(map, iter, val);
}
return kUpb_DecodeStatus_Ok;
}
////////////////////////////////////////////////////////////////////////////////
// OLD promotion functions, will be removed!
////////////////////////////////////////////////////////////////////////////////
// Warning: See TODO(b/267655898)
upb_UnknownToMessageRet upb_MiniTable_PromoteUnknownToMessage(
upb_Message* msg, const upb_MiniTable* mini_table,

@ -28,7 +28,9 @@
#ifndef UPB_MESSAGE_PROMOTE_H_
#define UPB_MESSAGE_PROMOTE_H_
#include "upb/collections/array.h"
#include "upb/message/extension_internal.h"
#include "upb/wire/decode.h"
// Must be last.
#include "upb/port/def.inc"
@ -91,6 +93,53 @@ typedef struct {
upb_Message* message;
} upb_UnknownToMessageRet;
// Promotes an "empty" non-repeated message field in `parent` to a message of
// the correct type.
//
// Preconditions:
//
// 1. The message field must currently be in the "empty" state (this must have
// been previously verified by the caller by calling
// `upb_Message_GetTaggedMessagePtr()` and observing that the message is
// indeed empty).
//
// 2. This `field` must have previously been linked.
//
// If the promotion succeeds, `parent` will have its data for `field` replaced
// by the promoted message, which is also returned in `*promoted`. If the
// return value indicates an error status, `parent` and `promoted` are
// unchanged.
upb_DecodeStatus upb_Message_PromoteMessage(upb_Message* parent,
const upb_MiniTable* mini_table,
const upb_MiniTableField* field,
int decode_options,
upb_Arena* arena,
upb_Message** promoted);
// Promotes any "empty" messages in this array to a message of the correct type
// `mini_table`. This function should only be called for arrays of messages.
//
// If the return value indicates an error status, some but not all elements may
// have been promoted, but the array itself will not be corrupted.
upb_DecodeStatus upb_Array_PromoteMessages(upb_Array* arr,
const upb_MiniTable* mini_table,
int decode_options,
upb_Arena* arena);
// Promotes any "empty" entries in this map to a message of the correct type
// `mini_table`. This function should only be called for maps that have a
// message type as the map value.
//
// If the return value indicates an error status, some but not all elements may
// have been promoted, but the map itself will not be corrupted.
upb_DecodeStatus upb_Map_PromoteMessages(upb_Map* map,
const upb_MiniTable* mini_table,
int decode_options, upb_Arena* arena);
////////////////////////////////////////////////////////////////////////////////
// OLD promotion interfaces, will be removed!
////////////////////////////////////////////////////////////////////////////////
// Promotes unknown data inside message to a upb_Message parsing the unknown.
//
// The unknown data is removed from message after field value is set

@ -41,12 +41,14 @@
#include "upb/base/string_view.h"
#include "upb/collections/array.h"
#include "upb/message/accessors.h"
#include "upb/message/copy.h"
#include "upb/mini_table/common.h"
#include "upb/mini_table/decode.h"
#include "upb/mini_table/encode_internal.hpp"
#include "upb/mini_table/field_internal.h"
#include "upb/test/test.upb.h"
#include "upb/upb.h"
#include "upb/upb.hpp"
#include "upb/wire/common.h"
#include "upb/wire/decode.h"
@ -266,6 +268,416 @@ upb_MiniTable* CreateMiniTableWithEmptySubTables(upb_Arena* arena) {
e.PutField(kUpb_FieldType_Message, 5, 0);
e.PutField(kUpb_FieldType_Message, 6, kUpb_FieldModifier_IsRepeated);
upb_Status status;
upb_Status_Clear(&status);
upb_MiniTable* table =
upb_MiniTable_Build(e.data().data(), e.data().size(), arena, &status);
EXPECT_EQ(status.ok, true);
return table;
}
upb_MiniTable* CreateMapEntryMiniTable(upb_Arena* arena) {
upb::MtDataEncoder e;
e.EncodeMap(kUpb_FieldType_Int32, kUpb_FieldType_Message, 0, 0);
upb_Status status;
upb_Status_Clear(&status);
upb_MiniTable* table =
upb_MiniTable_Build(e.data().data(), e.data().size(), arena, &status);
EXPECT_EQ(status.ok, true);
return table;
}
// Create a minitable to mimic ModelWithMaps with unlinked subs
// to lazily promote unknowns after parsing.
upb_MiniTable* CreateMiniTableWithEmptySubTablesForMaps(upb_Arena* arena) {
upb::MtDataEncoder e;
e.StartMessage(0);
e.PutField(kUpb_FieldType_Int32, 1, 0);
e.PutField(kUpb_FieldType_Message, 3, kUpb_FieldModifier_IsRepeated);
e.PutField(kUpb_FieldType_Message, 5, kUpb_FieldModifier_IsRepeated);
upb_Status status;
upb_Status_Clear(&status);
upb_MiniTable* table =
upb_MiniTable_Build(e.data().data(), e.data().size(), arena, &status);
// Field 5 corresponds to ModelWithMaps.map_sm.
upb_MiniTableField* map_field = const_cast<upb_MiniTableField*>(
upb_MiniTable_FindFieldByNumber(table, 5));
EXPECT_NE(map_field, nullptr);
upb_MiniTable* sub_table = CreateMapEntryMiniTable(arena);
upb_MiniTable_SetSubMessage(table, map_field, sub_table);
EXPECT_EQ(status.ok, true);
return table;
}
void CheckReserialize(const upb_Message* msg, const upb_MiniTable* mini_table,
upb_Arena* arena, char* serialized,
size_t serialized_size) {
// We can safely encode the "empty" message. We expect to get the same bytes
// out as were parsed.
size_t reserialized_size;
char* reserialized;
upb_EncodeStatus encode_status =
upb_Encode(msg, mini_table, kUpb_EncodeOption_Deterministic, arena,
&reserialized, &reserialized_size);
EXPECT_EQ(encode_status, kUpb_EncodeStatus_Ok);
EXPECT_EQ(reserialized_size, serialized_size);
EXPECT_EQ(0, memcmp(reserialized, serialized, serialized_size));
// We should get the same result if we copy+reserialize.
upb_Message* clone = upb_Message_DeepClone(msg, mini_table, arena);
encode_status = upb_Encode(clone, mini_table, kUpb_EncodeOption_Deterministic,
arena, &reserialized, &reserialized_size);
EXPECT_EQ(encode_status, kUpb_EncodeStatus_Ok);
EXPECT_EQ(reserialized_size, serialized_size);
EXPECT_EQ(0, memcmp(reserialized, serialized, serialized_size));
}
TEST(GeneratedCode, PromoteUnknownMessage) {
upb::Arena arena;
upb_test_ModelWithSubMessages* input_msg =
upb_test_ModelWithSubMessages_new(arena.ptr());
upb_test_ModelWithExtensions* sub_message =
upb_test_ModelWithExtensions_new(arena.ptr());
upb_test_ModelWithSubMessages_set_id(input_msg, 11);
upb_test_ModelWithExtensions_set_random_int32(sub_message, 12);
upb_test_ModelWithSubMessages_set_optional_child(input_msg, sub_message);
size_t serialized_size;
char* serialized = upb_test_ModelWithSubMessages_serialize(
input_msg, arena.ptr(), &serialized_size);
upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena.ptr());
upb_DecodeStatus decode_status;
// If we parse without allowing unlinked objects, the parse will fail.
// TODO(haberman): re-enable this test once the old method of tree shaking is
// removed
// upb_Message* fail_msg = _upb_Message_New(mini_table, arena.ptr());
// decode_status =
// upb_Decode(serialized, serialized_size, fail_msg, mini_table, nullptr,
// 0,
// arena.ptr());
// EXPECT_EQ(decode_status, kUpb_DecodeStatus_UnlinkedSubMessage);
// if we parse while allowing unlinked objects, the parse will succeed.
upb_Message* msg = _upb_Message_New(mini_table, arena.ptr());
decode_status =
upb_Decode(serialized, serialized_size, msg, mini_table, nullptr,
kUpb_DecodeOption_ExperimentalAllowUnlinked, arena.ptr());
EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
CheckReserialize(msg, mini_table, arena.ptr(), serialized, serialized_size);
// We can encode the "empty" message and get the same output bytes.
size_t reserialized_size;
char* reserialized;
upb_EncodeStatus encode_status = upb_Encode(
msg, mini_table, 0, arena.ptr(), &reserialized, &reserialized_size);
EXPECT_EQ(encode_status, kUpb_EncodeStatus_Ok);
EXPECT_EQ(reserialized_size, serialized_size);
EXPECT_EQ(0, memcmp(reserialized, serialized, serialized_size));
// Int32 field is present, as normal.
int32_t val = upb_Message_GetInt32(
msg, upb_MiniTable_FindFieldByNumber(mini_table, 4), 0);
EXPECT_EQ(val, 11);
// Unlinked sub-message is present, but getting the value returns NULL.
const upb_MiniTableField* submsg_field =
upb_MiniTable_FindFieldByNumber(mini_table, 5);
ASSERT_TRUE(submsg_field != nullptr);
EXPECT_TRUE(upb_Message_HasField(msg, submsg_field));
upb_TaggedMessagePtr tagged =
upb_Message_GetTaggedMessagePtr(msg, submsg_field, nullptr);
EXPECT_TRUE(upb_TaggedMessagePtr_IsEmpty(tagged));
// Update mini table and promote unknown to a message.
EXPECT_TRUE(
upb_MiniTable_SetSubMessage(mini_table, (upb_MiniTableField*)submsg_field,
&upb_test_ModelWithExtensions_msg_init));
const int decode_options = upb_DecodeOptions_MaxDepth(
kUpb_WireFormat_DefaultDepthLimit); // UPB_DECODE_ALIAS disabled.
upb_test_ModelWithExtensions* promoted;
upb_DecodeStatus promote_result =
upb_Message_PromoteMessage(msg, mini_table, submsg_field, decode_options,
arena.ptr(), (upb_Message**)&promoted);
EXPECT_EQ(promote_result, kUpb_DecodeStatus_Ok);
EXPECT_NE(nullptr, promoted);
EXPECT_EQ(promoted, upb_Message_GetMessage(msg, submsg_field, nullptr));
EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(promoted), 12);
}
// Tests a second parse that reuses an empty/unlinked message while the message
// is still unlinked.
TEST(GeneratedCode, ReparseUnlinked) {
upb::Arena arena;
upb_test_ModelWithSubMessages* input_msg =
upb_test_ModelWithSubMessages_new(arena.ptr());
upb_test_ModelWithExtensions* sub_message =
upb_test_ModelWithExtensions_new(arena.ptr());
upb_test_ModelWithSubMessages_set_id(input_msg, 11);
upb_test_ModelWithExtensions_add_repeated_int32(sub_message, 12, arena.ptr());
upb_test_ModelWithSubMessages_set_optional_child(input_msg, sub_message);
size_t serialized_size;
char* serialized = upb_test_ModelWithSubMessages_serialize(
input_msg, arena.ptr(), &serialized_size);
upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena.ptr());
// Parse twice without linking the MiniTable.
upb_Message* msg = _upb_Message_New(mini_table, arena.ptr());
upb_DecodeStatus decode_status =
upb_Decode(serialized, serialized_size, msg, mini_table, nullptr,
kUpb_DecodeOption_ExperimentalAllowUnlinked, arena.ptr());
EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
decode_status =
upb_Decode(serialized, serialized_size, msg, mini_table, nullptr,
kUpb_DecodeOption_ExperimentalAllowUnlinked, arena.ptr());
EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
// Update mini table and promote unknown to a message.
const upb_MiniTableField* submsg_field =
upb_MiniTable_FindFieldByNumber(mini_table, 5);
EXPECT_TRUE(
upb_MiniTable_SetSubMessage(mini_table, (upb_MiniTableField*)submsg_field,
&upb_test_ModelWithExtensions_msg_init));
const int decode_options = upb_DecodeOptions_MaxDepth(
kUpb_WireFormat_DefaultDepthLimit); // UPB_DECODE_ALIAS disabled.
upb_test_ModelWithExtensions* promoted;
upb_DecodeStatus promote_result =
upb_Message_PromoteMessage(msg, mini_table, submsg_field, decode_options,
arena.ptr(), (upb_Message**)&promoted);
EXPECT_EQ(promote_result, kUpb_DecodeStatus_Ok);
EXPECT_NE(nullptr, promoted);
EXPECT_EQ(promoted, upb_Message_GetMessage(msg, submsg_field, nullptr));
// The repeated field should have two entries for the two parses.
size_t repeated_size;
const int32_t* entries =
upb_test_ModelWithExtensions_repeated_int32(promoted, &repeated_size);
EXPECT_EQ(repeated_size, 2);
EXPECT_EQ(entries[0], 12);
EXPECT_EQ(entries[1], 12);
}
// Tests a second parse that promotes a message within the parser because we are
// merging into an empty/unlinked message after the message has been linked.
TEST(GeneratedCode, PromoteInParser) {
upb::Arena arena;
upb_test_ModelWithSubMessages* input_msg =
upb_test_ModelWithSubMessages_new(arena.ptr());
upb_test_ModelWithExtensions* sub_message =
upb_test_ModelWithExtensions_new(arena.ptr());
upb_test_ModelWithSubMessages_set_id(input_msg, 11);
upb_test_ModelWithExtensions_add_repeated_int32(sub_message, 12, arena.ptr());
upb_test_ModelWithSubMessages_set_optional_child(input_msg, sub_message);
size_t serialized_size;
char* serialized = upb_test_ModelWithSubMessages_serialize(
input_msg, arena.ptr(), &serialized_size);
upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena.ptr());
// Parse once without linking the MiniTable.
upb_Message* msg = _upb_Message_New(mini_table, arena.ptr());
upb_DecodeStatus decode_status =
upb_Decode(serialized, serialized_size, msg, mini_table, nullptr,
kUpb_DecodeOption_ExperimentalAllowUnlinked, arena.ptr());
EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
// Link the MiniTable.
const upb_MiniTableField* submsg_field =
upb_MiniTable_FindFieldByNumber(mini_table, 5);
EXPECT_TRUE(
upb_MiniTable_SetSubMessage(mini_table, (upb_MiniTableField*)submsg_field,
&upb_test_ModelWithExtensions_msg_init));
// Parse again. This will promote the message. An explicit promote will not
// be required.
decode_status =
upb_Decode(serialized, serialized_size, msg, mini_table, nullptr,
kUpb_DecodeOption_ExperimentalAllowUnlinked, arena.ptr());
EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
upb_test_ModelWithExtensions* promoted =
(upb_test_ModelWithExtensions*)upb_Message_GetMessage(msg, submsg_field,
nullptr);
EXPECT_NE(nullptr, promoted);
EXPECT_EQ(promoted, upb_Message_GetMessage(msg, submsg_field, nullptr));
// The repeated field should have two entries for the two parses.
size_t repeated_size;
const int32_t* entries =
upb_test_ModelWithExtensions_repeated_int32(promoted, &repeated_size);
EXPECT_EQ(repeated_size, 2);
EXPECT_EQ(entries[0], 12);
EXPECT_EQ(entries[1], 12);
}
TEST(GeneratedCode, PromoteUnknownRepeatedMessage) {
upb::Arena arena;
upb_test_ModelWithSubMessages* input_msg =
upb_test_ModelWithSubMessages_new(arena.ptr());
upb_test_ModelWithSubMessages_set_id(input_msg, 123);
// Add 2 repeated messages to input_msg.
upb_test_ModelWithExtensions* item =
upb_test_ModelWithSubMessages_add_items(input_msg, arena.ptr());
upb_test_ModelWithExtensions_set_random_int32(item, 5);
item = upb_test_ModelWithSubMessages_add_items(input_msg, arena.ptr());
upb_test_ModelWithExtensions_set_random_int32(item, 6);
size_t serialized_size;
char* serialized = upb_test_ModelWithSubMessages_serialize(
input_msg, arena.ptr(), &serialized_size);
upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena.ptr());
upb_DecodeStatus decode_status;
// If we parse without allowing unlinked objects, the parse will fail.
// TODO(haberman): re-enable this test once the old method of tree shaking is
// removed
// upb_Message* fail_msg = _upb_Message_New(mini_table, arena.ptr());
// decode_status =
// upb_Decode(serialized, serialized_size, fail_msg, mini_table, nullptr,
// 0,
// arena.ptr());
// EXPECT_EQ(decode_status, kUpb_DecodeStatus_UnlinkedSubMessage);
// if we parse while allowing unlinked objects, the parse will succeed.
upb_Message* msg = _upb_Message_New(mini_table, arena.ptr());
decode_status =
upb_Decode(serialized, serialized_size, msg, mini_table, nullptr,
kUpb_DecodeOption_ExperimentalAllowUnlinked, arena.ptr());
CheckReserialize(msg, mini_table, arena.ptr(), serialized, serialized_size);
// Int32 field is present, as normal.
EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
int32_t val = upb_Message_GetInt32(
msg, upb_MiniTable_FindFieldByNumber(mini_table, 4), 0);
EXPECT_EQ(val, 123);
const upb_MiniTableField* repeated_field =
upb_MiniTable_FindFieldByNumber(mini_table, 6);
upb_Array* array = upb_Message_GetMutableArray(msg, repeated_field);
// Array length is 2 even though the messages are empty.
EXPECT_EQ(2, upb_Array_Size(array));
// Update mini table and promote unknown to a message.
EXPECT_TRUE(upb_MiniTable_SetSubMessage(
mini_table, (upb_MiniTableField*)repeated_field,
&upb_test_ModelWithExtensions_msg_init));
const int decode_options = upb_DecodeOptions_MaxDepth(
kUpb_WireFormat_DefaultDepthLimit); // UPB_DECODE_ALIAS disabled.
upb_DecodeStatus promote_result =
upb_Array_PromoteMessages(array, &upb_test_ModelWithExtensions_msg_init,
decode_options, arena.ptr());
EXPECT_EQ(promote_result, kUpb_DecodeStatus_Ok);
const upb_Message* promoted_message = upb_Array_Get(array, 0).msg_val;
EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(
(upb_test_ModelWithExtensions*)promoted_message),
5);
promoted_message = upb_Array_Get(array, 1).msg_val;
EXPECT_EQ(upb_test_ModelWithExtensions_random_int32(
(upb_test_ModelWithExtensions*)promoted_message),
6);
}
TEST(GeneratedCode, PromoteUnknownToMap) {
upb::Arena arena;
upb_test_ModelWithMaps* input_msg = upb_test_ModelWithMaps_new(arena.ptr());
upb_test_ModelWithMaps_set_id(input_msg, 123);
upb_test_ModelWithExtensions* submsg1 =
upb_test_ModelWithExtensions_new(arena.ptr());
upb_test_ModelWithExtensions_set_random_int32(submsg1, 123);
upb_test_ModelWithExtensions* submsg2 =
upb_test_ModelWithExtensions_new(arena.ptr());
upb_test_ModelWithExtensions_set_random_int32(submsg2, 456);
// Add 2 map entries.
upb_test_ModelWithMaps_map_im_set(input_msg, 111, submsg1, arena.ptr());
upb_test_ModelWithMaps_map_im_set(input_msg, 222, submsg2, arena.ptr());
size_t serialized_size;
char* serialized = upb_test_ModelWithMaps_serialize_ex(
input_msg, kUpb_EncodeOption_Deterministic, arena.ptr(),
&serialized_size);
upb_MiniTable* mini_table =
CreateMiniTableWithEmptySubTablesForMaps(arena.ptr());
// If we parse without allowing unlinked objects, the parse will fail.
upb_Message* fail_msg1 = _upb_Message_New(mini_table, arena.ptr());
upb_DecodeStatus decode_status =
upb_Decode(serialized, serialized_size, fail_msg1, mini_table, nullptr, 0,
arena.ptr());
EXPECT_EQ(decode_status, kUpb_DecodeStatus_UnlinkedSubMessage);
// if we parse while allowing unlinked objects, the parse will succeed.
upb_Message* msg = _upb_Message_New(mini_table, arena.ptr());
decode_status =
upb_Decode(serialized, serialized_size, msg, mini_table, nullptr,
kUpb_DecodeOption_ExperimentalAllowUnlinked, arena.ptr());
EXPECT_EQ(decode_status, kUpb_DecodeStatus_Ok);
CheckReserialize(msg, mini_table, arena.ptr(), serialized, serialized_size);
upb_MiniTableField* map_field = const_cast<upb_MiniTableField*>(
upb_MiniTable_FindFieldByNumber(mini_table, 5));
upb_Map* map = upb_Message_GetMutableMap(msg, map_field);
// Map size is 2 even though messages are unlinked.
EXPECT_EQ(2, upb_Map_Size(map));
// Update mini table and promote unknown to a message.
upb_MiniTable* entry = const_cast<upb_MiniTable*>(
upb_MiniTable_GetSubMessageTable(mini_table, map_field));
upb_MiniTableField* entry_value = const_cast<upb_MiniTableField*>(
upb_MiniTable_FindFieldByNumber(entry, 2));
upb_MiniTable_SetSubMessage(entry, entry_value,
&upb_test_ModelWithExtensions_msg_init);
upb_DecodeStatus promote_result = upb_Map_PromoteMessages(
map, &upb_test_ModelWithExtensions_msg_init, 0, arena.ptr());
EXPECT_EQ(promote_result, kUpb_DecodeStatus_Ok);
upb_MessageValue key;
upb_MessageValue val;
key.int32_val = 111;
EXPECT_TRUE(upb_Map_Get(map, key, &val));
EXPECT_EQ(123,
upb_test_ModelWithExtensions_random_int32(
static_cast<const upb_test_ModelWithExtensions*>(val.msg_val)));
key.int32_val = 222;
EXPECT_TRUE(upb_Map_Get(map, key, &val));
EXPECT_EQ(456,
upb_test_ModelWithExtensions_random_int32(
static_cast<const upb_test_ModelWithExtensions*>(val.msg_val)));
}
} // namespace
// OLD tests, to be removed!
namespace {
// Create a minitable to mimic ModelWithSubMessages with unlinked subs
// to lazily promote unknowns after parsing.
upb_MiniTable* CreateMiniTableWithEmptySubTablesOld(upb_Arena* arena) {
upb::MtDataEncoder e;
e.StartMessage(0);
e.PutField(kUpb_FieldType_Int32, 4, 0);
e.PutField(kUpb_FieldType_Message, 5, 0);
e.PutField(kUpb_FieldType_Message, 6, kUpb_FieldModifier_IsRepeated);
upb_Status status;
upb_Status_Clear(&status);
upb_MiniTable* table =
@ -275,16 +687,14 @@ upb_MiniTable* CreateMiniTableWithEmptySubTables(upb_Arena* arena) {
// since it checks ->ext on parameter.
upb_MiniTableSub* sub = const_cast<upb_MiniTableSub*>(
&table->subs[table->fields[1].UPB_PRIVATE(submsg_index)]);
sub->submsg = nullptr;
sub = const_cast<upb_MiniTableSub*>(
&table->subs[table->fields[2].UPB_PRIVATE(submsg_index)]);
sub->submsg = nullptr;
return table;
}
// Create a minitable to mimic ModelWithMaps with unlinked subs
// to lazily promote unknowns after parsing.
upb_MiniTable* CreateMiniTableWithEmptySubTablesForMaps(upb_Arena* arena) {
upb_MiniTable* CreateMiniTableWithEmptySubTablesForMapsOld(upb_Arena* arena) {
upb::MtDataEncoder e;
e.StartMessage(0);
e.PutField(kUpb_FieldType_Int32, 1, 0);
@ -300,14 +710,12 @@ upb_MiniTable* CreateMiniTableWithEmptySubTablesForMaps(upb_Arena* arena) {
// since it checks ->ext on parameter.
upb_MiniTableSub* sub = const_cast<upb_MiniTableSub*>(
&table->subs[table->fields[1].UPB_PRIVATE(submsg_index)]);
sub->submsg = nullptr;
sub = const_cast<upb_MiniTableSub*>(
&table->subs[table->fields[2].UPB_PRIVATE(submsg_index)]);
sub->submsg = nullptr;
return table;
}
upb_MiniTable* CreateMapEntryMiniTable(upb_Arena* arena) {
upb_MiniTable* CreateMapEntryMiniTableOld(upb_Arena* arena) {
upb::MtDataEncoder e;
e.EncodeMap(kUpb_FieldType_String, kUpb_FieldType_String, 0, 0);
upb_Status status;
@ -318,7 +726,7 @@ upb_MiniTable* CreateMapEntryMiniTable(upb_Arena* arena) {
return table;
}
TEST(GeneratedCode, PromoteUnknownMessage) {
TEST(GeneratedCode, PromoteUnknownMessageOld) {
upb_Arena* arena = upb_Arena_New();
upb_test_ModelWithSubMessages* input_msg =
upb_test_ModelWithSubMessages_new(arena);
@ -331,7 +739,7 @@ TEST(GeneratedCode, PromoteUnknownMessage) {
char* serialized = upb_test_ModelWithSubMessages_serialize(input_msg, arena,
&serialized_size);
upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena);
upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTablesOld(arena);
upb_Message* msg = _upb_Message_New(mini_table, arena);
upb_DecodeStatus decode_status = upb_Decode(serialized, serialized_size, msg,
mini_table, nullptr, 0, arena);
@ -361,7 +769,7 @@ TEST(GeneratedCode, PromoteUnknownMessage) {
upb_Arena_Free(arena);
}
TEST(GeneratedCode, PromoteUnknownRepeatedMessage) {
TEST(GeneratedCode, PromoteUnknownRepeatedMessageOld) {
upb_Arena* arena = upb_Arena_New();
upb_test_ModelWithSubMessages* input_msg =
upb_test_ModelWithSubMessages_new(arena);
@ -378,7 +786,7 @@ TEST(GeneratedCode, PromoteUnknownRepeatedMessage) {
char* serialized = upb_test_ModelWithSubMessages_serialize(input_msg, arena,
&serialized_size);
upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTables(arena);
upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTablesOld(arena);
upb_Message* msg = _upb_Message_New(mini_table, arena);
upb_DecodeStatus decode_status = upb_Decode(serialized, serialized_size, msg,
mini_table, nullptr, 0, arena);
@ -416,7 +824,7 @@ TEST(GeneratedCode, PromoteUnknownRepeatedMessage) {
upb_Arena_Free(arena);
}
TEST(GeneratedCode, PromoteUnknownToMap) {
TEST(GeneratedCode, PromoteUnknownToMapOld) {
upb_Arena* arena = upb_Arena_New();
upb_test_ModelWithMaps* input_msg = upb_test_ModelWithMaps_new(arena);
upb_test_ModelWithMaps_set_id(input_msg, 123);
@ -433,8 +841,9 @@ TEST(GeneratedCode, PromoteUnknownToMap) {
char* serialized =
upb_test_ModelWithMaps_serialize(input_msg, arena, &serialized_size);
upb_MiniTable* mini_table = CreateMiniTableWithEmptySubTablesForMaps(arena);
upb_MiniTable* map_entry_mini_table = CreateMapEntryMiniTable(arena);
upb_MiniTable* mini_table =
CreateMiniTableWithEmptySubTablesForMapsOld(arena);
upb_MiniTable* map_entry_mini_table = CreateMapEntryMiniTableOld(arena);
upb_Message* msg = _upb_Message_New(mini_table, arena);
const int decode_options =
upb_DecodeOptions_MaxDepth(kUpb_WireFormat_DefaultDepthLimit);

@ -122,7 +122,10 @@ UPB_API_INLINE bool upb_MiniTableField_HasPresence(
UPB_API_INLINE const upb_MiniTable* upb_MiniTable_GetSubMessageTable(
const upb_MiniTable* mini_table, const upb_MiniTableField* field) {
UPB_ASSERT(upb_MiniTableField_CType(field) == kUpb_CType_Message);
return mini_table->subs[field->UPB_PRIVATE(submsg_index)].submsg;
const upb_MiniTable* ret =
mini_table->subs[field->UPB_PRIVATE(submsg_index)].submsg;
UPB_ASSUME(ret);
return ret == &_kUpb_MiniTable_Empty ? NULL : ret;
}
// Returns the MiniTableEnum for this enum field. If the field is unlinked,

@ -447,7 +447,7 @@ static void upb_MtDecoder_AllocateSubs(upb_MtDecoder* d,
upb_MtDecoder_CheckOutOfMemory(d, subs);
uint32_t i = 0;
for (; i < sub_counts.submsg_count; i++) {
subs[i].submsg = NULL; // &kUpb_MiniTable_Empty;
subs[i].submsg = &_kUpb_MiniTable_Empty;
}
if (sub_counts.subenum_count) {
upb_MiniTableField* f = d->fields;
@ -1062,6 +1062,9 @@ bool upb_MiniTable_SetSubMessage(upb_MiniTable* table,
upb_MiniTableSub* table_sub =
(void*)&table->subs[field->UPB_PRIVATE(submsg_index)];
// TODO(haberman): Add this assert back once YouTube is updated to not call
// this function repeatedly.
// UPB_ASSERT(table_sub->submsg == &_kUpb_MiniTable_Empty);
table_sub->submsg = sub;
return true;
}

@ -239,7 +239,7 @@ TEST(MiniTableEnumTest, Enum) {
}
}
TEST_P(MiniTableTest, SubsInitializedToNull) {
TEST_P(MiniTableTest, SubsInitializedToEmpty) {
upb::Arena arena;
upb::MtDataEncoder e;
// Create mini table with 2 message fields.
@ -251,8 +251,8 @@ TEST_P(MiniTableTest, SubsInitializedToNull) {
e.data().data(), e.data().size(), GetParam(), arena.ptr(), status.ptr());
ASSERT_NE(nullptr, table);
EXPECT_EQ(table->field_count, 2);
EXPECT_EQ(table->subs[0].submsg, nullptr);
EXPECT_EQ(table->subs[1].submsg, nullptr);
EXPECT_EQ(table->subs[0].submsg, &_kUpb_MiniTable_Empty);
EXPECT_EQ(table->subs[1].submsg, &_kUpb_MiniTable_Empty);
}
TEST(MiniTableEnumTest, PositiveAndNegative) {

@ -0,0 +1,39 @@
/*
* Copyright (c) 2023, Google LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Google LLC nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "upb/mini_table/message_internal.h"
const upb_MiniTable _kUpb_MiniTable_Empty = {
.subs = NULL,
.fields = NULL,
.size = 0,
.field_count = 0,
.ext = kUpb_ExtMode_NonExtendable,
.dense_below = 0,
.table_mask = -1,
.required_count = 0,
};

@ -115,6 +115,9 @@ typedef struct {
extern "C" {
#endif
// A MiniTable for an empty message, used for unlinked sub-messages.
extern const upb_MiniTable _kUpb_MiniTable_Empty;
// Computes a bitmask in which the |l->required_count| lowest bits are set,
// except that we skip the lowest bit (because upb never uses hasbit 0).
//

@ -28,6 +28,15 @@
#ifndef UPB_MINI_TABLE_TYPES_H_
#define UPB_MINI_TABLE_TYPES_H_
#include <stdint.h>
// Must be last.
#include "upb/port/def.inc"
#ifdef __cplusplus
extern "C" {
#endif
typedef void upb_Message;
typedef struct upb_MiniTable upb_MiniTable;
@ -37,4 +46,51 @@ typedef struct upb_MiniTableField upb_MiniTableField;
typedef struct upb_MiniTableFile upb_MiniTableFile;
typedef union upb_MiniTableSub upb_MiniTableSub;
// When a upb_Message* is stored in a message, array, or map, it is stored in a
// tagged form. If the tag bit is set, the referenced upb_Message is of type
// _kUpb_MiniTable_Empty (a sentinel message type with no fields) instead of
// that field's true message type. This forms the basis of what we call
// "dynamic tree shaking."
//
// See the documentation for kUpb_DecodeOption_ExperimentalAllowUnlinked for
// more information.
typedef uintptr_t upb_TaggedMessagePtr;
// Internal-only because empty messages cannot be created by the user.
UPB_INLINE upb_TaggedMessagePtr _upb_TaggedMessagePtr_Pack(upb_Message* ptr,
bool empty) {
UPB_ASSERT(((uintptr_t)ptr & 1) == 0);
return (uintptr_t)ptr | (empty ? 1 : 0);
}
// Users who enable unlinked sub-messages must use this to test whether a
// message is empty before accessing it. If a message is empty, it must be
// first promoted using the interfaces in message/promote.h.
UPB_INLINE bool upb_TaggedMessagePtr_IsEmpty(upb_TaggedMessagePtr ptr) {
return ptr & 1;
}
UPB_INLINE upb_Message* _upb_TaggedMessagePtr_GetMessage(
upb_TaggedMessagePtr ptr) {
return (upb_Message*)(ptr & ~(uintptr_t)1);
}
UPB_INLINE upb_Message* upb_TaggedMessagePtr_GetNonEmptyMessage(
upb_TaggedMessagePtr ptr) {
UPB_ASSERT(!upb_TaggedMessagePtr_IsEmpty(ptr));
return _upb_TaggedMessagePtr_GetMessage(ptr);
}
UPB_INLINE upb_Message* _upb_TaggedMessagePtr_GetEmptyMessage(
upb_TaggedMessagePtr ptr) {
UPB_ASSERT(upb_TaggedMessagePtr_IsEmpty(ptr));
return _upb_TaggedMessagePtr_GetMessage(ptr);
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#include "upb/port/undef.inc"
#endif /* UPB_MINI_TABLE_TYPES_H_ */

@ -55,6 +55,7 @@ message EmptyMessageWithExtensions {
message ModelWithExtensions {
optional int32 random_int32 = 3;
optional string random_name = 4;
repeated int32 repeated_int32 = 5;
// Reserved for unknown fields/extensions test.
extensions 1000 to max;
}
@ -87,6 +88,7 @@ message ModelWithMaps {
optional int32 id = 1;
map<string, string> map_ss = 3;
map<int32, int32> map_ii = 4;
map<int32, ModelWithExtensions> map_im = 5;
}
message ExtremeDefaults {

@ -33,6 +33,7 @@
#include "upb/collections/array_internal.h"
#include "upb/collections/map_internal.h"
#include "upb/mem/arena_internal.h"
#include "upb/message/accessors_internal.h"
#include "upb/mini_table/common.h"
#include "upb/mini_table/enum_internal.h"
#include "upb/port/atomic.h"
@ -218,16 +219,53 @@ static void _upb_Decoder_Munge(int type, wireval* val) {
}
}
static upb_Message* _upb_Decoder_NewSubMessage(
upb_Decoder* d, const upb_MiniTableSub* subs,
const upb_MiniTableField* field) {
static upb_Message* _upb_Decoder_NewSubMessage(upb_Decoder* d,
const upb_MiniTableSub* subs,
const upb_MiniTableField* field,
upb_TaggedMessagePtr* target) {
const upb_MiniTable* subl = subs[field->UPB_PRIVATE(submsg_index)].submsg;
UPB_ASSERT(subl);
upb_Message* msg = _upb_Message_New(subl, &d->arena);
if (!msg) _upb_Decoder_ErrorJmp(d, kUpb_DecodeStatus_OutOfMemory);
// Extensions should not be unlinked. A message extension should not be
// registered until its sub-message type is available to be linked.
bool is_empty = subl == &_kUpb_MiniTable_Empty;
bool is_extension = field->mode & kUpb_LabelFlags_IsExtension;
UPB_ASSERT(!(is_empty && is_extension));
if (is_empty && !(d->options & kUpb_DecodeOption_ExperimentalAllowUnlinked)) {
_upb_Decoder_ErrorJmp(d, kUpb_DecodeStatus_UnlinkedSubMessage);
}
upb_TaggedMessagePtr tagged = _upb_TaggedMessagePtr_Pack(msg, is_empty);
memcpy(target, &tagged, sizeof(tagged));
return msg;
}
static upb_Message* _upb_Decoder_ReuseSubMessage(
upb_Decoder* d, const upb_MiniTableSub* subs,
const upb_MiniTableField* field, upb_TaggedMessagePtr* target) {
upb_TaggedMessagePtr tagged = *target;
const upb_MiniTable* subl = subs[field->UPB_PRIVATE(submsg_index)].submsg;
UPB_ASSERT(subl);
if (!upb_TaggedMessagePtr_IsEmpty(tagged) || subl == &_kUpb_MiniTable_Empty) {
return _upb_TaggedMessagePtr_GetMessage(tagged);
}
// We found an empty message from a previous parse that was performed before
// this field was linked. But it is linked now, so we want to allocate a new
// message of the correct type and promote data into it before continuing.
upb_Message* existing = _upb_TaggedMessagePtr_GetEmptyMessage(tagged);
upb_Message* promoted = _upb_Decoder_NewSubMessage(d, subs, field, target);
size_t size;
const char* unknown = upb_Message_GetUnknown(existing, &size);
upb_DecodeStatus status = upb_Decode(unknown, size, promoted, subl, d->extreg,
d->options, &d->arena);
if (status != kUpb_DecodeStatus_Ok) _upb_Decoder_ErrorJmp(d, status);
return promoted;
}
static const char* _upb_Decoder_ReadString(upb_Decoder* d, const char* ptr,
int size, upb_StringView* str) {
const char* str_ptr = ptr;
@ -514,9 +552,9 @@ static const char* _upb_Decoder_DecodeToArray(upb_Decoder* d, const char* ptr,
}
case kUpb_DecodeOp_SubMessage: {
/* Append submessage / group. */
upb_Message* submsg = _upb_Decoder_NewSubMessage(d, subs, field);
*UPB_PTR_AT(_upb_array_ptr(arr), arr->size * sizeof(void*),
upb_Message*) = submsg;
upb_TaggedMessagePtr* target = UPB_PTR_AT(
_upb_array_ptr(arr), arr->size * sizeof(void*), upb_TaggedMessagePtr);
upb_Message* submsg = _upb_Decoder_NewSubMessage(d, subs, field, target);
arr->size++;
if (UPB_UNLIKELY(field->UPB_PRIVATE(descriptortype) ==
kUpb_FieldType_Group)) {
@ -590,6 +628,7 @@ static const char* _upb_Decoder_DecodeToMap(upb_Decoder* d, const char* ptr,
UPB_ASSERT(upb_MiniTableField_Type(field) == kUpb_FieldType_Message);
const upb_MiniTable* entry = subs[field->UPB_PRIVATE(submsg_index)].submsg;
UPB_ASSERT(entry);
UPB_ASSERT(entry->field_count == 2);
UPB_ASSERT(!upb_IsRepeatedOrMap(&entry->fields[0]));
UPB_ASSERT(!upb_IsRepeatedOrMap(&entry->fields[1]));
@ -604,13 +643,10 @@ static const char* _upb_Decoder_DecodeToMap(upb_Decoder* d, const char* ptr,
if (entry->fields[1].UPB_PRIVATE(descriptortype) == kUpb_FieldType_Message ||
entry->fields[1].UPB_PRIVATE(descriptortype) == kUpb_FieldType_Group) {
const upb_MiniTable* submsg_table = entry->subs[0].submsg;
// Any sub-message entry must be linked. We do not allow dynamic tree
// shaking in this case.
UPB_ASSERT(submsg_table);
// Create proactively to handle the case where it doesn't appear. */
ent.data.v.val = upb_value_ptr(_upb_Message_New(submsg_table, &d->arena));
// Create proactively to handle the case where it doesn't appear.
upb_TaggedMessagePtr msg;
_upb_Decoder_NewSubMessage(d, entry->subs, &entry->fields[1], &msg);
ent.data.v.val = upb_value_uintptr(msg);
}
ptr =
@ -670,11 +706,12 @@ static const char* _upb_Decoder_DecodeToSubMessage(
/* Store into message. */
switch (op) {
case kUpb_DecodeOp_SubMessage: {
upb_Message** submsgp = mem;
upb_Message* submsg = *submsgp;
if (!submsg) {
submsg = _upb_Decoder_NewSubMessage(d, subs, field);
*submsgp = submsg;
upb_TaggedMessagePtr* submsgp = mem;
upb_Message* submsg;
if (*submsgp) {
submsg = _upb_Decoder_ReuseSubMessage(d, subs, field, submsgp);
} else {
submsg = _upb_Decoder_NewSubMessage(d, subs, field, submsgp);
}
if (UPB_UNLIKELY(type == kUpb_FieldType_Group)) {
ptr = _upb_Decoder_DecodeKnownGroup(d, ptr, submsg, subs, field);
@ -778,11 +815,10 @@ static void upb_Decoder_AddKnownMessageSetItem(
if (UPB_UNLIKELY(!ext)) {
_upb_Decoder_ErrorJmp(d, kUpb_DecodeStatus_OutOfMemory);
}
upb_Message* submsg =
_upb_Decoder_NewSubMessage(d, &ext->ext->sub, &ext->ext->field);
upb_Message* submsg = _upb_Decoder_NewSubMessage(
d, &ext->ext->sub, &ext->ext->field, (upb_TaggedMessagePtr*)&ext->data);
upb_DecodeStatus status = upb_Decode(data, size, submsg, item_mt->sub.submsg,
d->extreg, d->options, &d->arena);
memcpy(&ext->data, &submsg, sizeof(submsg));
if (status != kUpb_DecodeStatus_Ok) _upb_Decoder_ErrorJmp(d, status);
}
@ -961,13 +997,16 @@ int _upb_Decoder_GetVarintOp(const upb_MiniTableField* field) {
}
UPB_FORCEINLINE
static void _upb_Decoder_CheckUnlinked(const upb_MiniTable* mt,
static void _upb_Decoder_CheckUnlinked(upb_Decoder* d, const upb_MiniTable* mt,
const upb_MiniTableField* field,
int* op) {
// If sub-message is not linked, treat as unknown.
if (field->mode & kUpb_LabelFlags_IsExtension) return;
const upb_MiniTableSub* sub = &mt->subs[field->UPB_PRIVATE(submsg_index)];
if (sub->submsg) return;
if ((d->options & kUpb_DecodeOption_ExperimentalAllowUnlinked) ||
sub->submsg != &_kUpb_MiniTable_Empty) {
return;
}
#ifndef NDEBUG
const upb_MiniTableField* oneof = upb_MiniTable_GetOneof(mt, field);
if (oneof) {
@ -984,7 +1023,7 @@ static void _upb_Decoder_CheckUnlinked(const upb_MiniTable* mt,
*op = kUpb_DecodeOp_UnknownField;
}
int _upb_Decoder_GetDelimitedOp(const upb_MiniTable* mt,
int _upb_Decoder_GetDelimitedOp(upb_Decoder* d, const upb_MiniTable* mt,
const upb_MiniTableField* field) {
enum { kRepeatedBase = 19 };
@ -1039,7 +1078,7 @@ int _upb_Decoder_GetDelimitedOp(const upb_MiniTable* mt,
int op = kDelimitedOps[ndx];
if (op == kUpb_DecodeOp_SubMessage) {
_upb_Decoder_CheckUnlinked(mt, field, &op);
_upb_Decoder_CheckUnlinked(d, mt, field, &op);
}
return op;
@ -1079,13 +1118,13 @@ static const char* _upb_Decoder_DecodeWireValue(upb_Decoder* d, const char* ptr,
return upb_WireReader_ReadFixed64(ptr, &val->uint64_val);
case kUpb_WireType_Delimited:
ptr = upb_Decoder_DecodeSize(d, ptr, &val->size);
*op = _upb_Decoder_GetDelimitedOp(mt, field);
*op = _upb_Decoder_GetDelimitedOp(d, mt, field);
return ptr;
case kUpb_WireType_StartGroup:
val->uint32_val = field->number;
if (field->UPB_PRIVATE(descriptortype) == kUpb_FieldType_Group) {
*op = kUpb_DecodeOp_SubMessage;
_upb_Decoder_CheckUnlinked(mt, field, op);
_upb_Decoder_CheckUnlinked(d, mt, field, op);
} else if (field->UPB_PRIVATE(descriptortype) ==
kUpb_FakeFieldType_MessageSetItem) {
*op = kUpb_DecodeOp_MessageSetItem;

@ -64,6 +64,41 @@ enum {
* implemting ParseFromString() semantics. For MergeFromString(), a
* post-parse validation step will always be necessary. */
kUpb_DecodeOption_CheckRequired = 2,
/* EXPERIMENTAL:
*
* If set, the parser will allow parsing of sub-message fields that were not
* previously linked using upb_MiniTable_SetSubMessage(). The data will be
* parsed into an internal "empty" message type that cannot be accessed
* directly, but can be later promoted into the true message type if the
* sub-message fields are linked at a later time.
*
* Users should set this option if they intend to perform dynamic tree shaking
* and promoting using the interfaces in message/promote.h. If this option is
* enabled, it is important that the resulting messages are only accessed by
* code that is aware of promotion rules:
*
* 1. Message pointers in upb_Message, upb_Array, and upb_Map are represented
* by a tagged pointer upb_TaggedMessagePointer. The tag indicates whether
* the message uses the internal "empty" type.
*
* 2. Any code *reading* these message pointers must test whether the "empty"
* tag bit is set, using the interfaces in mini_table/types.h. However
* writing of message pointers should always use plain upb_Message*, since
* users are not allowed to create "empty" messages.
*
* 3. It is always safe to test whether a field is present or test the array
* length; these interfaces will reflect that empty messages are present,
* even though their data cannot be accessed without promoting first.
*
* 4. If a message pointer is indeed tagged as empty, the message may not be
* accessed directly, only promoted through the interfaces in
* message/promote.h.
*
* 5. Tagged/empty messages may never be created by the user. They may only
* be created by the parser or the message-copying logic in message/copy.h.
*/
kUpb_DecodeOption_ExperimentalAllowUnlinked = 4,
};
UPB_INLINE uint32_t upb_DecodeOptions_MaxDepth(uint16_t depth) {
@ -92,6 +127,11 @@ typedef enum {
// kUpb_DecodeOption_CheckRequired failed (see above), but the parse otherwise
// succeeded.
kUpb_DecodeStatus_MissingRequired = 5,
// Unlinked sub-message field was present, but
// kUpb_DecodeOptions_ExperimentalAllowUnlinked was not specified in the list
// of options.
kUpb_DecodeStatus_UnlinkedSubMessage = 6,
} upb_DecodeStatus;
UPB_API upb_DecodeStatus upb_Decode(const char* buf, size_t size,

@ -213,6 +213,15 @@ static void encode_fixedarray(upb_encstate* e, const upb_Array* arr,
static void encode_message(upb_encstate* e, const upb_Message* msg,
const upb_MiniTable* m, size_t* size);
static void encode_TaggedMessagePtr(upb_encstate* e,
upb_TaggedMessagePtr tagged,
const upb_MiniTable* m, size_t* size) {
if (upb_TaggedMessagePtr_IsEmpty(tagged)) {
m = &_kUpb_MiniTable_Empty;
}
encode_message(e, _upb_TaggedMessagePtr_GetMessage(tagged), m, size);
}
static void encode_scalar(upb_encstate* e, const void* _field_mem,
const upb_MiniTableSub* subs,
const upb_MiniTableField* f) {
@ -262,27 +271,27 @@ static void encode_scalar(upb_encstate* e, const void* _field_mem,
}
case kUpb_FieldType_Group: {
size_t size;
void* submsg = *(void**)field_mem;
upb_TaggedMessagePtr submsg = *(upb_TaggedMessagePtr*)field_mem;
const upb_MiniTable* subm = subs[f->UPB_PRIVATE(submsg_index)].submsg;
if (submsg == NULL) {
if (submsg == 0) {
return;
}
if (--e->depth == 0) encode_err(e, kUpb_EncodeStatus_MaxDepthExceeded);
encode_tag(e, f->number, kUpb_WireType_EndGroup);
encode_message(e, submsg, subm, &size);
encode_TaggedMessagePtr(e, submsg, subm, &size);
wire_type = kUpb_WireType_StartGroup;
e->depth++;
break;
}
case kUpb_FieldType_Message: {
size_t size;
void* submsg = *(void**)field_mem;
upb_TaggedMessagePtr submsg = *(upb_TaggedMessagePtr*)field_mem;
const upb_MiniTable* subm = subs[f->UPB_PRIVATE(submsg_index)].submsg;
if (submsg == NULL) {
if (submsg == 0) {
return;
}
if (--e->depth == 0) encode_err(e, kUpb_EncodeStatus_MaxDepthExceeded);
encode_message(e, submsg, subm, &size);
encode_TaggedMessagePtr(e, submsg, subm, &size);
encode_varint(e, size);
wire_type = kUpb_WireType_Delimited;
e->depth++;
@ -364,29 +373,29 @@ static void encode_array(upb_encstate* e, const upb_Message* msg,
return;
}
case kUpb_FieldType_Group: {
const void* const* start = _upb_array_constptr(arr);
const void* const* ptr = start + arr->size;
const upb_TaggedMessagePtr* start = _upb_array_constptr(arr);
const upb_TaggedMessagePtr* ptr = start + arr->size;
const upb_MiniTable* subm = subs[f->UPB_PRIVATE(submsg_index)].submsg;
if (--e->depth == 0) encode_err(e, kUpb_EncodeStatus_MaxDepthExceeded);
do {
size_t size;
ptr--;
encode_tag(e, f->number, kUpb_WireType_EndGroup);
encode_message(e, *ptr, subm, &size);
encode_TaggedMessagePtr(e, *ptr, subm, &size);
encode_tag(e, f->number, kUpb_WireType_StartGroup);
} while (ptr != start);
e->depth++;
return;
}
case kUpb_FieldType_Message: {
const void* const* start = _upb_array_constptr(arr);
const void* const* ptr = start + arr->size;
const upb_TaggedMessagePtr* start = _upb_array_constptr(arr);
const upb_TaggedMessagePtr* ptr = start + arr->size;
const upb_MiniTable* subm = subs[f->UPB_PRIVATE(submsg_index)].submsg;
if (--e->depth == 0) encode_err(e, kUpb_EncodeStatus_MaxDepthExceeded);
do {
size_t size;
ptr--;
encode_message(e, *ptr, subm, &size);
encode_TaggedMessagePtr(e, *ptr, subm, &size);
encode_varint(e, size);
encode_tag(e, f->number, kUpb_WireType_Delimited);
} while (ptr != start);

Loading…
Cancel
Save