|
|
|
// 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_INTERNAL_ACCESSORS_H_
|
|
|
|
#define UPB_MESSAGE_INTERNAL_ACCESSORS_H_
|
|
|
|
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "upb/base/descriptor_constants.h"
|
|
|
|
#include "upb/base/string_view.h"
|
|
|
|
#include "upb/mem/arena.h"
|
|
|
|
#include "upb/message/internal/extension.h"
|
|
|
|
#include "upb/message/internal/map.h"
|
|
|
|
#include "upb/message/internal/message.h"
|
|
|
|
#include "upb/message/internal/size_log2.h"
|
|
|
|
#include "upb/message/internal/types.h"
|
|
|
|
#include "upb/message/map.h"
|
|
|
|
#include "upb/message/message.h"
|
|
|
|
#include "upb/message/tagged_ptr.h"
|
|
|
|
#include "upb/mini_table/extension.h"
|
|
|
|
#include "upb/mini_table/field.h"
|
|
|
|
#include "upb/mini_table/internal/field.h"
|
|
|
|
|
|
|
|
// Must be last.
|
|
|
|
#include "upb/port/def.inc"
|
|
|
|
|
|
|
|
#if defined(__GNUC__) && !defined(__clang__)
|
|
|
|
// GCC raises incorrect warnings in these functions. It thinks that we are
|
|
|
|
// overrunning buffers, but we carefully write the functions in this file to
|
|
|
|
// guarantee that this is impossible. GCC gets this wrong due it its failure
|
|
|
|
// to perform constant propagation as we expect:
|
|
|
|
// - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108217
|
|
|
|
// - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108226
|
|
|
|
//
|
|
|
|
// Unfortunately this also indicates that GCC is not optimizing away the
|
|
|
|
// switch() in cases where it should be, compromising the performance.
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
#pragma GCC diagnostic ignored "-Warray-bounds"
|
|
|
|
#pragma GCC diagnostic ignored "-Wstringop-overflow"
|
|
|
|
#if __GNUC__ >= 11
|
|
|
|
#pragma GCC diagnostic ignored "-Wstringop-overread"
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
extern "C" {
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// LINT.IfChange(presence_logic)
|
|
|
|
|
|
|
|
// Hasbit access ///////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
UPB_INLINE size_t _upb_Hasbit_Offset(size_t index) { return index / 8; }
|
|
|
|
|
|
|
|
UPB_INLINE char _upb_Hasbit_Mask(size_t index) { return 1 << (index % 8); }
|
|
|
|
|
|
|
|
UPB_INLINE size_t _upb_Hasbit_Index(const upb_MiniTableField* f) {
|
|
|
|
UPB_ASSERT(f->presence > 0);
|
|
|
|
return f->presence;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE bool _upb_Message_GetHasbitByIndex(const upb_Message* msg,
|
|
|
|
size_t index) {
|
|
|
|
const size_t offset = _upb_Hasbit_Offset(index);
|
|
|
|
const char mask = _upb_Hasbit_Mask(index);
|
|
|
|
return (*UPB_PTR_AT(msg, offset, const char) & mask) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_SetHasbitByIndex(const upb_Message* msg,
|
|
|
|
size_t index) {
|
|
|
|
const size_t offset = _upb_Hasbit_Offset(index);
|
|
|
|
const char mask = _upb_Hasbit_Mask(index);
|
|
|
|
(*UPB_PTR_AT(msg, offset, char)) |= mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_ClearHasbitByIndex(const upb_Message* msg,
|
|
|
|
size_t index) {
|
|
|
|
const size_t offset = _upb_Hasbit_Offset(index);
|
|
|
|
const char mask = _upb_Hasbit_Mask(index);
|
|
|
|
(*UPB_PTR_AT(msg, offset, char)) &= ~mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE bool _upb_Message_GetHasbitByField(const upb_Message* msg,
|
|
|
|
const upb_MiniTableField* f) {
|
|
|
|
return _upb_Message_GetHasbitByIndex(msg, _upb_Hasbit_Index(f));
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_SetHasbitByField(const upb_Message* msg,
|
|
|
|
const upb_MiniTableField* f) {
|
|
|
|
_upb_Message_SetHasbitByIndex(msg, _upb_Hasbit_Index(f));
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_ClearHasbitByField(const upb_Message* msg,
|
|
|
|
const upb_MiniTableField* f) {
|
|
|
|
_upb_Message_ClearHasbitByIndex(msg, _upb_Hasbit_Index(f));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Oneof case access ///////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
UPB_INLINE size_t _upb_OneofCase_Offset(const upb_MiniTableField* f) {
|
|
|
|
UPB_ASSERT(f->presence < 0);
|
|
|
|
return ~(ptrdiff_t)f->presence;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE uint32_t* _upb_Message_OneofCasePtr(upb_Message* msg,
|
|
|
|
const upb_MiniTableField* f) {
|
|
|
|
return UPB_PTR_AT(msg, _upb_OneofCase_Offset(f), uint32_t);
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE uint32_t _upb_Message_GetOneofCase(const upb_Message* msg,
|
|
|
|
const upb_MiniTableField* f) {
|
|
|
|
return *_upb_Message_OneofCasePtr((upb_Message*)msg, f);
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_SetOneofCase(upb_Message* msg,
|
|
|
|
const upb_MiniTableField* f) {
|
|
|
|
*_upb_Message_OneofCasePtr(msg, f) = f->number;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: implement _upb_Message_ClearOneofCase()
|
|
|
|
|
|
|
|
// LINT.ThenChange(GoogleInternalName2)
|
|
|
|
|
|
|
|
UPB_INLINE bool _upb_MiniTableField_InOneOf(const upb_MiniTableField* field) {
|
|
|
|
return field->presence < 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void* _upb_MiniTableField_GetPtr(upb_Message* msg,
|
|
|
|
const upb_MiniTableField* field) {
|
|
|
|
return (char*)msg + field->offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE const void* _upb_MiniTableField_GetConstPtr(
|
|
|
|
const upb_Message* msg, const upb_MiniTableField* field) {
|
|
|
|
return (char*)msg + field->offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_SetPresence(upb_Message* msg,
|
|
|
|
const upb_MiniTableField* field) {
|
|
|
|
if (field->presence > 0) {
|
|
|
|
_upb_Message_SetHasbitByField(msg, field);
|
|
|
|
} else if (_upb_MiniTableField_InOneOf(field)) {
|
|
|
|
_upb_Message_SetOneofCase(msg, field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE bool _upb_MiniTable_ValueIsNonZero(const void* default_val,
|
|
|
|
const upb_MiniTableField* field) {
|
|
|
|
char zero[16] = {0};
|
|
|
|
switch (_upb_MiniTableField_GetRep(field)) {
|
|
|
|
case kUpb_FieldRep_1Byte:
|
|
|
|
return memcmp(&zero, default_val, 1) != 0;
|
|
|
|
case kUpb_FieldRep_4Byte:
|
|
|
|
return memcmp(&zero, default_val, 4) != 0;
|
|
|
|
case kUpb_FieldRep_8Byte:
|
|
|
|
return memcmp(&zero, default_val, 8) != 0;
|
|
|
|
case kUpb_FieldRep_StringView: {
|
|
|
|
const upb_StringView* sv = (const upb_StringView*)default_val;
|
|
|
|
return sv->size != 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UPB_UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_MiniTable_CopyFieldData(void* to, const void* from,
|
|
|
|
const upb_MiniTableField* field) {
|
|
|
|
switch (_upb_MiniTableField_GetRep(field)) {
|
|
|
|
case kUpb_FieldRep_1Byte:
|
|
|
|
memcpy(to, from, 1);
|
|
|
|
return;
|
|
|
|
case kUpb_FieldRep_4Byte:
|
|
|
|
memcpy(to, from, 4);
|
|
|
|
return;
|
|
|
|
case kUpb_FieldRep_8Byte:
|
|
|
|
memcpy(to, from, 8);
|
|
|
|
return;
|
|
|
|
case kUpb_FieldRep_StringView: {
|
|
|
|
memcpy(to, from, sizeof(upb_StringView));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UPB_UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE size_t
|
|
|
|
_upb_MiniTable_ElementSizeLg2(const upb_MiniTableField* field) {
|
|
|
|
return upb_SizeLog2_FieldType(
|
|
|
|
(upb_FieldType)field->UPB_PRIVATE(descriptortype));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Here we define universal getter/setter functions for message fields.
|
|
|
|
// These look very branchy and inefficient, but as long as the MiniTableField
|
|
|
|
// values are known at compile time, all the branches are optimized away and
|
|
|
|
// we are left with ideal code. This can happen either through through
|
|
|
|
// literals or UPB_ASSUME():
|
|
|
|
//
|
|
|
|
// // Via struct literals.
|
|
|
|
// bool FooMessage_set_bool_field(const upb_Message* msg, bool val) {
|
|
|
|
// const upb_MiniTableField field = {1, 0, 0, /* etc... */};
|
|
|
|
// // All value in "field" are compile-time known.
|
|
|
|
// _upb_Message_SetNonExtensionField(msg, &field, &value);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // Via UPB_ASSUME().
|
|
|
|
// UPB_INLINE bool upb_Message_SetBool(upb_Message* msg,
|
|
|
|
// const upb_MiniTableField* field,
|
|
|
|
// bool value, upb_Arena* a) {
|
|
|
|
// UPB_ASSUME(field->UPB_PRIVATE(descriptortype) == kUpb_FieldType_Bool);
|
|
|
|
// UPB_ASSUME(!upb_MiniTableField_IsRepeatedOrMap(field));
|
|
|
|
// UPB_ASSUME(_upb_MiniTableField_GetRep(field) == kUpb_FieldRep_1Byte);
|
|
|
|
// _upb_Message_SetField(msg, field, &value, a);
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// As a result, we can use these universal getters/setters for *all* message
|
|
|
|
// accessors: generated code, MiniTable accessors, and reflection. The only
|
|
|
|
// exception is the binary encoder/decoder, which need to be a bit more clever
|
|
|
|
// about how they read/write the message data, for efficiency.
|
|
|
|
//
|
|
|
|
// These functions work on both extensions and non-extensions. If the field
|
|
|
|
// of a setter is known to be a non-extension, the arena may be NULL and the
|
|
|
|
// returned bool value may be ignored since it will always succeed.
|
|
|
|
|
|
|
|
UPB_INLINE bool _upb_Message_HasExtensionField(
|
|
|
|
const upb_Message* msg, const upb_MiniTableExtension* ext) {
|
|
|
|
UPB_ASSERT(upb_MiniTableField_HasPresence(&ext->field));
|
|
|
|
return _upb_Message_Getext(msg, ext) != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE bool _upb_Message_HasNonExtensionField(
|
|
|
|
const upb_Message* msg, const upb_MiniTableField* field) {
|
|
|
|
UPB_ASSERT(upb_MiniTableField_HasPresence(field));
|
|
|
|
UPB_ASSUME(!upb_MiniTableField_IsExtension(field));
|
|
|
|
if (_upb_MiniTableField_InOneOf(field)) {
|
|
|
|
return _upb_Message_GetOneofCase(msg, field) == field->number;
|
|
|
|
} else {
|
|
|
|
return _upb_Message_GetHasbitByField(msg, field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static UPB_FORCEINLINE void _upb_Message_GetNonExtensionField(
|
|
|
|
const upb_Message* msg, const upb_MiniTableField* field,
|
|
|
|
const void* default_val, void* val) {
|
|
|
|
UPB_ASSUME(!upb_MiniTableField_IsExtension(field));
|
|
|
|
if ((_upb_MiniTableField_InOneOf(field) ||
|
|
|
|
_upb_MiniTable_ValueIsNonZero(default_val, field)) &&
|
|
|
|
!_upb_Message_HasNonExtensionField(msg, field)) {
|
|
|
|
_upb_MiniTable_CopyFieldData(val, default_val, field);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_upb_MiniTable_CopyFieldData(val, _upb_MiniTableField_GetConstPtr(msg, field),
|
|
|
|
field);
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_GetExtensionField(
|
|
|
|
const upb_Message* msg, const upb_MiniTableExtension* mt_ext,
|
|
|
|
const void* default_val, void* val) {
|
|
|
|
UPB_ASSUME(upb_MiniTableField_IsExtension(&mt_ext->field));
|
|
|
|
const upb_Message_Extension* ext = _upb_Message_Getext(msg, mt_ext);
|
|
|
|
if (ext) {
|
|
|
|
_upb_MiniTable_CopyFieldData(val, &ext->data, &mt_ext->field);
|
|
|
|
} else {
|
|
|
|
_upb_MiniTable_CopyFieldData(val, default_val, &mt_ext->field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_GetField(const upb_Message* msg,
|
|
|
|
const upb_MiniTableField* field,
|
|
|
|
const void* default_val, void* val) {
|
|
|
|
if (upb_MiniTableField_IsExtension(field)) {
|
|
|
|
_upb_Message_GetExtensionField(msg, (upb_MiniTableExtension*)field,
|
|
|
|
default_val, val);
|
|
|
|
} else {
|
|
|
|
_upb_Message_GetNonExtensionField(msg, field, default_val, val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_SetNonExtensionField(
|
|
|
|
upb_Message* msg, const upb_MiniTableField* field, const void* val) {
|
|
|
|
UPB_ASSUME(!upb_MiniTableField_IsExtension(field));
|
|
|
|
_upb_Message_SetPresence(msg, field);
|
|
|
|
_upb_MiniTable_CopyFieldData(_upb_MiniTableField_GetPtr(msg, field), val,
|
|
|
|
field);
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE bool _upb_Message_SetExtensionField(
|
|
|
|
upb_Message* msg, const upb_MiniTableExtension* mt_ext, const void* val,
|
|
|
|
upb_Arena* a) {
|
|
|
|
UPB_ASSERT(a);
|
|
|
|
upb_Message_Extension* ext =
|
|
|
|
_upb_Message_GetOrCreateExtension(msg, mt_ext, a);
|
|
|
|
if (!ext) return false;
|
|
|
|
_upb_MiniTable_CopyFieldData(&ext->data, val, &mt_ext->field);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE bool _upb_Message_SetField(upb_Message* msg,
|
|
|
|
const upb_MiniTableField* field,
|
|
|
|
const void* val, upb_Arena* a) {
|
|
|
|
if (upb_MiniTableField_IsExtension(field)) {
|
|
|
|
const upb_MiniTableExtension* ext = (const upb_MiniTableExtension*)field;
|
|
|
|
return _upb_Message_SetExtensionField(msg, ext, val, a);
|
|
|
|
} else {
|
|
|
|
_upb_Message_SetNonExtensionField(msg, field, val);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_ClearExtensionField(
|
|
|
|
upb_Message* msg, const upb_MiniTableExtension* ext_l) {
|
|
|
|
upb_Message_Internal* in = upb_Message_Getinternal(msg);
|
|
|
|
if (!in->internal) return;
|
|
|
|
const upb_Message_Extension* base =
|
|
|
|
UPB_PTR_AT(in->internal, in->internal->ext_begin, upb_Message_Extension);
|
|
|
|
upb_Message_Extension* ext =
|
|
|
|
(upb_Message_Extension*)_upb_Message_Getext(msg, ext_l);
|
|
|
|
if (ext) {
|
|
|
|
*ext = *base;
|
|
|
|
in->internal->ext_begin += sizeof(upb_Message_Extension);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UPB_INLINE void _upb_Message_ClearNonExtensionField(
|
|
|
|
upb_Message* msg, const upb_MiniTableField* field) {
|
|
|
|
if (field->presence > 0) {
|
|
|
|
_upb_Message_ClearHasbitByField(msg, field);
|
|
|
|
} else if (_upb_MiniTableField_InOneOf(field)) {
|
|
|
|
uint32_t* ptr = _upb_Message_OneofCasePtr(msg, field);
|
|
|
|
if (*ptr != field->number) return;
|
|
|
|
*ptr = 0;
|
|
|
|
}
|
|
|
|
const char zeros[16] = {0};
|
|
|
|
_upb_MiniTable_CopyFieldData(_upb_MiniTableField_GetPtr(msg, field), zeros,
|
|
|
|
field);
|
|
|
|
}
|
|
|
|
|
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
2 years ago
|
|
|
UPB_INLINE void _upb_Message_AssertMapIsUntagged(
|
|
|
|
const upb_Message* msg, const upb_MiniTableField* field) {
|
|
|
|
UPB_UNUSED(msg);
|
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
2 years ago
|
|
|
_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);
|
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
2 years ago
|
|
|
_upb_Message_AssertMapIsUntagged(msg, field);
|
|
|
|
upb_Map* map = NULL;
|
|
|
|
upb_Map* default_map_value = NULL;
|
|
|
|
_upb_Message_GetNonExtensionField(msg, field, &default_map_value, &map);
|
|
|
|
if (!map) {
|
|
|
|
map = _upb_Map_New(arena, key_size, val_size);
|
|
|
|
// Check again due to: https://godbolt.org/z/7WfaoKG1r
|
|
|
|
_upb_MiniTableField_CheckIsMap(field);
|
|
|
|
_upb_Message_SetNonExtensionField(msg, field, &map);
|
|
|
|
}
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
} /* extern "C" */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(__GNUC__) && !defined(__clang__)
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "upb/port/undef.inc"
|
|
|
|
|
|
|
|
#endif // UPB_MESSAGE_INTERNAL_ACCESSORS_H_
|