diff --git a/src/google/protobuf/compiler/cpp/parse_function_generator.cc b/src/google/protobuf/compiler/cpp/parse_function_generator.cc index a0773f0bac..a8573ec10d 100644 --- a/src/google/protobuf/compiler/cpp/parse_function_generator.cc +++ b/src/google/protobuf/compiler/cpp/parse_function_generator.cc @@ -72,6 +72,12 @@ bool HasWeakFields(const Descriptor* descriptor) { } bool UseDirectTcParserTable(const FieldDescriptor* field, const Options& options) { + if (options.opensource_runtime) { + // We can only use direct tables when the default instances are constant + // initialized. In OSS we support some compilers that do not provide this + // requirement, so disable the direct use. + return false; + } if (field->cpp_type() != field->CPPTYPE_MESSAGE) return false; auto* m = field->message_type(); return !m->options().message_set_wire_format() && @@ -533,7 +539,9 @@ void ParseFunctionGenerator::GenerateTailCallTable(Formatter& format) { format("{_fl::Offset{sizeof($classname$::Impl_::Split)}},\n"); break; case TailCallTableInfo::kSubMessage: - format("{::_pbi::FieldAuxDefaultMessage{}, &$1$},\n", + format("{::_pbi::FieldAuxMessageCreator<$1$>{}, &$2$},\n", + QualifiedClassName(aux_entry.field->message_type(), + options_), QualifiedDefaultInstanceName( aux_entry.field->message_type(), options_)); break; diff --git a/src/google/protobuf/generated_message_tctable_decl.h b/src/google/protobuf/generated_message_tctable_decl.h index 380a03ae22..4a6a29842e 100644 --- a/src/google/protobuf/generated_message_tctable_decl.h +++ b/src/google/protobuf/generated_message_tctable_decl.h @@ -165,6 +165,30 @@ struct Offset { struct FieldAuxDefaultMessage {}; +template +struct FieldAuxMessageCreator {}; + +template +MessageLite* NewImpl(Arena* arena) { + return Arena::CreateMessage(arena); +} + +#if defined(_MSC_VER) +// In some versions of MSVC the globals are not properly constant initialized +// unless they are marked `constexpr`, which the default instances are not. +// In that case, we can't use them during parse because we need to be able to +// parse during dynamic initialization. +template +constexpr std::false_type CanUseDefaultInstanceForNew() { + return {}; +} +#else +template +constexpr std::true_type CanUseDefaultInstanceForNew() { + return {}; +} +#endif + // Small type card used by mini parse to handle map entries. // Map key/values are very limited, so we can encode the whole thing in a single // byte. @@ -411,13 +435,24 @@ struct alignas(uint64_t) TcParseTableBase { : enum_range{range_start, range_length} {} constexpr FieldAux(const MessageLite* msg) : message_default_p(msg) {} constexpr FieldAux(FieldAuxDefaultMessage, const void* msg) + : message_default_p(msg) { + ABSL_ASSERT(CanUseDefaultInstanceForNew()); + } + template (), int> = 0> + constexpr FieldAux(FieldAuxMessageCreator, const void* msg) : message_default_p(msg) {} + template (), int> = 0> + constexpr FieldAux(FieldAuxMessageCreator, const void*) + : creator(&NewImpl) {} constexpr FieldAux(const TcParseTableBase* table) : table(table) {} constexpr FieldAux(MapAuxInfo map_info) : map_info(map_info) {} constexpr FieldAux(void (*create_in_arena)(Arena*, void*)) : create_in_arena(create_in_arena) {} constexpr FieldAux(LazyEagerVerifyFnType verify_func) : verify_func(verify_func) {} + bool (*enum_validator)(int); struct { int16_t start; // minimum enum number (if it fits) @@ -425,15 +460,18 @@ struct alignas(uint64_t) TcParseTableBase { } enum_range; uint32_t offset; const void* message_default_p; + MessageLite* (*creator)(Arena* arena); const TcParseTableBase* table; MapAuxInfo map_info; void (*create_in_arena)(Arena*, void*); LazyEagerVerifyFnType verify_func; const MessageLite* message_default() const { + ABSL_DCHECK(CanUseDefaultInstanceForNew()); return static_cast(message_default_p); } const MessageLite* message_default_weak() const { + ABSL_DCHECK(CanUseDefaultInstanceForNew()); return *static_cast(message_default_p); } }; diff --git a/src/google/protobuf/generated_message_tctable_impl.h b/src/google/protobuf/generated_message_tctable_impl.h index 532bee0700..2ed87dde15 100644 --- a/src/google/protobuf/generated_message_tctable_impl.h +++ b/src/google/protobuf/generated_message_tctable_impl.h @@ -811,6 +811,10 @@ class PROTOBUF_EXPORT TcParser final { const char* ptr, Arena* arena, SerialArena* serial_arena, ParseContext* ctx, RepeatedPtrField& field); + static MessageLite* NewMessage(Arena* arena, TcParseTableBase::FieldAux aux); + static MessageLite* AddMessage(RepeatedPtrFieldBase& field, + TcParseTableBase::FieldAux aux); + static void AddUnknownEnum(MessageLite* msg, const TcParseTableBase* table, uint32_t tag, int32_t enum_value); diff --git a/src/google/protobuf/generated_message_tctable_lite.cc b/src/google/protobuf/generated_message_tctable_lite.cc index cf7b904de6..21821a5abf 100644 --- a/src/google/protobuf/generated_message_tctable_lite.cc +++ b/src/google/protobuf/generated_message_tctable_lite.cc @@ -425,6 +425,7 @@ inline PROTOBUF_ALWAYS_INLINE const char* TcParser::SingularParseMessageAuxImpl( if (aux_is_table) { const auto* inner_table = table->field_aux(data.aux_idx())->table; if (field == nullptr) { + ABSL_DCHECK(CanUseDefaultInstanceForNew()); field = inner_table->default_instance->New(msg->GetArenaForAllocation()); } if (group_coding) { @@ -434,9 +435,8 @@ inline PROTOBUF_ALWAYS_INLINE const char* TcParser::SingularParseMessageAuxImpl( return ctx->ParseMessage(field, ptr, inner_table); } else { if (field == nullptr) { - const MessageLite* default_instance = - table->field_aux(data.aux_idx())->message_default(); - field = default_instance->New(msg->GetArenaForAllocation()); + field = NewMessage(msg->GetArenaForAllocation(), + *table->field_aux(data.aux_idx())); } if (group_coding) { return ctx->ParseGroup(field, ptr, FastDecodeTag(saved_tag)); @@ -510,9 +510,10 @@ inline PROTOBUF_ALWAYS_INLINE const char* TcParser::RepeatedParseMessageAuxImpl( auto& field = RefAt(msg, data.offset()); do { ptr += sizeof(TagType); - MessageLite* submsg = field.Add>( - aux_is_table ? aux.table->default_instance : aux.message_default()); if (aux_is_table) { + ABSL_DCHECK(CanUseDefaultInstanceForNew()); + MessageLite* submsg = field.Add>( + aux.table->default_instance); if (group_coding) { ptr = ctx->ParseGroup(submsg, ptr, FastDecodeTag(expected_tag), aux.table); @@ -520,6 +521,7 @@ inline PROTOBUF_ALWAYS_INLINE const char* TcParser::RepeatedParseMessageAuxImpl( ptr = ctx->ParseMessage(submsg, ptr, aux.table); } } else { + MessageLite* submsg = AddMessage(field, aux); if (group_coding) { ptr = ctx->ParseGroup(submsg, ptr, FastDecodeTag(expected_tag)); } else { @@ -1741,6 +1743,7 @@ void* TcParser::MaybeGetSplitBase(MessageLite* msg, const bool is_split, void* out = msg; if (is_split) { const uint32_t split_offset = GetSplitOffset(table); + ABSL_DCHECK(CanUseDefaultInstanceForNew()); void* default_split = TcParser::RefAt(table->default_instance, split_offset); void*& split = TcParser::RefAt(msg, split_offset); @@ -2110,6 +2113,27 @@ bool TcParser::MpVerifyUtf8(const absl::Cord& wire_bytes, } } +PROTOBUF_ALWAYS_INLINE MessageLite* TcParser::AddMessage( + RepeatedPtrFieldBase& field, TcParseTableBase::FieldAux aux) { + if (CanUseDefaultInstanceForNew()) { + return field.Add>(aux.message_default()); + } else { + MessageLite* value = + field.AddFromCleared>(); + if (value == nullptr) { + value = aux.creator(field.GetArena()); + field.AddAllocated>(value); + } + return value; + } +} + +PROTOBUF_ALWAYS_INLINE MessageLite* TcParser::NewMessage( + Arena* arena, TcParseTableBase::FieldAux aux) { + return CanUseDefaultInstanceForNew() ? aux.message_default()->New(arena) + : aux.creator(arena); +} + template PROTOBUF_NOINLINE const char* TcParser::MpString(PROTOBUF_TC_PARAM_DECL) { const auto& entry = RefAt(table, data.entry_offset()); @@ -2310,6 +2334,7 @@ PROTOBUF_NOINLINE const char* TcParser::MpMessage(PROTOBUF_TC_PARAM_DECL) { if ((type_card & field_layout::kTvMask) == field_layout::kTvTable) { auto* inner_table = table->field_aux(&entry)->table; if (need_init || field == nullptr) { + ABSL_DCHECK(CanUseDefaultInstanceForNew()); field = inner_table->default_instance->New(msg->GetArenaForAllocation()); } if (is_group) { @@ -2318,15 +2343,15 @@ PROTOBUF_NOINLINE const char* TcParser::MpMessage(PROTOBUF_TC_PARAM_DECL) { return ctx->ParseMessage(field, ptr, inner_table); } else { if (need_init || field == nullptr) { - const MessageLite* def; if ((type_card & field_layout::kTvMask) == field_layout::kTvDefault) { - def = table->field_aux(&entry)->message_default(); + field = + NewMessage(msg->GetArenaForAllocation(), *table->field_aux(&entry)); } else { ABSL_DCHECK_EQ(type_card & field_layout::kTvMask, +field_layout::kTvWeakPtr); - def = table->field_aux(&entry)->message_default_weak(); + field = table->field_aux(&entry)->message_default_weak()->New( + msg->GetArenaForAllocation()); } - field = def->New(msg->GetArenaForAllocation()); } if (is_group) { return ctx->ParseGroup(field, ptr, decoded_tag); @@ -2375,15 +2400,15 @@ const char* TcParser::MpRepeatedMessage(PROTOBUF_TC_PARAM_DECL) { } return ctx->ParseMessage(value, ptr, inner_table); } else { - const MessageLite* def; + MessageLite* value; if ((type_card & field_layout::kTvMask) == field_layout::kTvDefault) { - def = aux.message_default(); + value = AddMessage(field, aux); } else { ABSL_DCHECK_EQ(type_card & field_layout::kTvMask, +field_layout::kTvWeakPtr); - def = aux.message_default_weak(); + value = field.Add>( + aux.message_default_weak()); } - MessageLite* value = field.Add>(def); if (is_group) { return ctx->ParseGroup(value, ptr, decoded_tag); } diff --git a/src/google/protobuf/message.cc b/src/google/protobuf/message.cc index fc474dd7c1..1c5507d6be 100644 --- a/src/google/protobuf/message.cc +++ b/src/google/protobuf/message.cc @@ -162,14 +162,20 @@ void Message::DiscardUnknownFields() { const char* Message::_InternalParse(const char* ptr, internal::ParseContext* ctx) { #if defined(PROTOBUF_USE_TABLE_PARSER_ON_REFLECTION) - auto meta = GetMetadata(); - ptr = internal::TcParser::ParseLoop(this, ptr, ctx, - meta.reflection->GetTcParseTable()); - - return ptr; + // The reflection based TDP builder will use default instances. If we can't do + // it disable the whole thing and fallback to the reflection parser. + constexpr bool kUseTcParser = internal::CanUseDefaultInstanceForNew(); #else - return WireFormat::_InternalParse(this, ptr, ctx); + constexpr bool kUseTcParser = false; #endif + if (kUseTcParser) { + auto meta = GetMetadata(); + ptr = internal::TcParser::ParseLoop(this, ptr, ctx, + meta.reflection->GetTcParseTable()); + + return ptr; + } + return WireFormat::_InternalParse(this, ptr, ctx); } uint8_t* Message::_InternalSerialize(uint8_t* target,