Check consistency of ClassData types of RepeatedPtrField members in the destructor. The repeated field destructor caches the element destructor and calls it on each element of a repeated field, rather than going through full dynamic dispatch for each element. Currently, if the elements of a repeated field are of different types, this code may segfault in non-obvious ways (e.g. mismatch size-delete, or memory corruption). The DCHECK gives a better error message when you have repeated fields of mixed element types. This code is only active with `enable_custom_vtable`. PiperOrigin-RevId: 966610091
diff --git a/src/google/protobuf/class_data.h b/src/google/protobuf/class_data.h index 806aab2..8fed1be 100644 --- a/src/google/protobuf/class_data.h +++ b/src/google/protobuf/class_data.h
@@ -16,6 +16,7 @@ #include <cstddef> #include <cstdint> +#include <string> #include "absl/log/absl_check.h" #include "google/protobuf/port.h" @@ -227,6 +228,8 @@ << " and " << from.GetTypeName(); this->merge_to_from(to, from); } + + std::string DebugName() const; }; #ifndef PROTOBUF_MESSAGE_GLOBALS
diff --git a/src/google/protobuf/message_lite.cc b/src/google/protobuf/message_lite.cc index 755b2a9..23f810f 100644 --- a/src/google/protobuf/message_lite.cc +++ b/src/google/protobuf/message_lite.cc
@@ -202,6 +202,16 @@ namespace internal { +std::string ClassData::DebugName() const { + absl::string_view type_name = TypeIdFromClassData(this).name(); + if (is_dynamic) { + return absl::StrCat(type_name, " (dynamic, class_data = 0x", + absl::Hex(this), ")"); + } else { + return std::string(type_name); + } +} + void FailDynamicCast( const MessageLite& from, std::variant<const char*, const MessageLite*> to_type_name) {
diff --git a/src/google/protobuf/repeated_ptr_field.cc b/src/google/protobuf/repeated_ptr_field.cc index 9a96301..3915804 100644 --- a/src/google/protobuf/repeated_ptr_field.cc +++ b/src/google/protobuf/repeated_ptr_field.cc
@@ -27,6 +27,7 @@ #include "google/protobuf/message_traits.h" #include "google/protobuf/port.h" #include "google/protobuf/repeated_field.h" +#include "google/protobuf/type_id.h" // Must be included last. #include "google/protobuf/port_def.inc" @@ -118,6 +119,13 @@ absl::PrefetchToLocalCacheNta(elems[i + 5]); } auto* ptr = cast<H>(elems[i]); + + ABSL_DCHECK_EQ(GetClassData(*ptr), class_data) + << "Type mismatch in RepeatedPtrFieldBase::DestroyMessageLites: found " + "element of type " + << GetClassData(*ptr)->DebugName() << " at index " << i + << " in a repeated field of " << class_data->DebugName(); + destroy(*ptr); internal::SizedDelete(ptr, allocation_size); }
diff --git a/src/google/protobuf/repeated_ptr_field_unittest.cc b/src/google/protobuf/repeated_ptr_field_unittest.cc index 87e179f..2f3a62b 100644 --- a/src/google/protobuf/repeated_ptr_field_unittest.cc +++ b/src/google/protobuf/repeated_ptr_field_unittest.cc
@@ -326,6 +326,40 @@ EXPECT_GE(field.SpaceUsedExcludingSelf(), min_expected_usage); } +TEST_F(RepeatedPtrFieldTest, DestroyErroneousIncorrectElement) { +#if defined(NDEBUG) || !defined(PROTOBUF_CUSTOM_VTABLE) + GTEST_SKIP() << "The `DCHECK` is disabled in release builds / " + "!PROTOBUF_CUSTOM_VTABLE."; +#else + auto destroy_wrong_element_type = []() { + RepeatedPtrField<TestAllTypes> field; + field.AddAllocated(reinterpret_cast<TestAllTypes*>( + new proto2_unittest::NestedTestAllTypes)); + }; + // The destructor of `RepeatedPtrField` verifies that all elements are of the + // expected type. + ASSERT_DEATH(destroy_wrong_element_type(), + "Type mismatch in RepeatedPtrFieldBase::DestroyMessageLites"); +#endif +} + +TEST_F(RepeatedPtrFieldTest, DestroyErroneousMixedElements) { +#if defined(NDEBUG) || !defined(PROTOBUF_CUSTOM_VTABLE) + GTEST_SKIP() << "The `DCHECK` is disabled in release builds / " + "!PROTOBUF_CUSTOM_VTABLE."; +#else + auto destroy_heterogenous_repeated_field = []() { + RepeatedPtrField<google::protobuf::MessageLite> field; + field.AddAllocated(new TestAllTypes); + field.AddAllocated(new proto2_unittest::NestedTestAllTypes); + }; + // The destructor of `RepeatedPtrField` verifies that all elements are of the + // same type if the element type is `Message` or `MessageLite`. + ASSERT_DEATH(destroy_heterogenous_repeated_field(), + "Type mismatch in RepeatedPtrFieldBase::DestroyMessageLites"); +#endif +} + namespace { template <typename Elem>
diff --git a/src/google/protobuf/type_id.h b/src/google/protobuf/type_id.h index 3679a09..5dd5561 100644 --- a/src/google/protobuf/type_id.h +++ b/src/google/protobuf/type_id.h
@@ -23,9 +23,11 @@ namespace protobuf { class MessageLite; +class TypeId; namespace internal { struct ClassData; +TypeId TypeIdFromClassData(const ClassData* data); } // namespace internal // A `std::type_info` equivalent for protobuf message types. @@ -97,11 +99,21 @@ } private: + friend TypeId internal::TypeIdFromClassData(const internal::ClassData* data); + constexpr explicit TypeId(const internal::ClassData* data) : data_(data) {} const internal::ClassData* data_; }; +namespace internal { + +inline TypeId TypeIdFromClassData(const ClassData* data) { + return TypeId(data); +} + +} // namespace internal + } // namespace protobuf } // namespace google