diff --git a/CHANGES.txt b/CHANGES.txt index fc30d1ae22153..1b831847597e5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,19 +1,18 @@ 2022-07-01 Unreleased version - * Add TransferAllMessages() util function - * Break ZeroCopyOutputByteSink into its own file. - * Explicitly instantiate InternalMetadata members at UnknownFieldSet. - * Support kdoc for Kotlin - * Delete unused function: IsProto3Field. + C++ + * Reduced .pb.o object file size slightly by explicitly instantiating + InternalMetadata templates in the runtime. * Add C++20 keywords guarded by PROTOBUF_FUTURE_CPP20_KEYWORDS - * Escape Kotlin keywords in package names in proto generated code - * Fix StrAppend crashing on empty strings. - -2022-06-27 Unreleased version - * Handle reflection for message splitting. - * make metadata fields lazy. - * Extend visibility of plugin library to upb - * Modernize conformance_cpp.cc. - * Don't request 64-byte alignment unless the toolchain supports it. + * Fixed crash in ThreadLocalStorage for pre-C++17 compilers on 32-bit ARM. + * Clarified that JSON API non-OK statuses are not a stable API. + + Kotlin + * Kotlin generated code comments now use kdoc format instead of javadoc. + * Escape keywords in package names in proto generated code + + Java + * Performance improvement for repeated use of FieldMaskUtil#merge by caching + constructed FieldMaskTrees. 2022-06-27 version 21.2 (C++/Java/Python/PHP/Objective-C/C#/Ruby) C++ diff --git a/benchmarks/java/src/main/java/com/google/protobuf/ProtoCaliperBenchmark.java b/benchmarks/java/src/main/java/com/google/protobuf/ProtoCaliperBenchmark.java index adf5a2abbf07c..061981eacfb3a 100644 --- a/benchmarks/java/src/main/java/com/google/protobuf/ProtoCaliperBenchmark.java +++ b/benchmarks/java/src/main/java/com/google/protobuf/ProtoCaliperBenchmark.java @@ -12,9 +12,8 @@ import java.util.ArrayList; import java.util.List; -/** - * Basic benchmarks for Java protobuf parsing. - */ +/** Basic benchmarks for Java protobuf parsing. */ +@SuppressWarnings("CheckReturnValue") public class ProtoCaliperBenchmark { public enum BenchmarkMessageType { GOOGLE_MESSAGE1_PROTO3 { diff --git a/java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java b/java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java index 1270ef06ee4da..9ea7f93ffdb15 100644 --- a/java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java +++ b/java/core/src/test/java/com/google/protobuf/LiteEqualsAndHashTest.java @@ -30,14 +30,13 @@ package com.google.protobuf; +import junit.framework.TestCase; import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Bar; import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.BarPrime; import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Foo; import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.TestOneofEquals; import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.TestRecursiveOneof; -import junit.framework.TestCase; - /** * Test generate equal and hash methods for the lite runtime. * @@ -120,6 +119,6 @@ private void assertEqualsAndHashCodeAreFalse(Object o1, Object o2) { public void testRecursiveHashcode() { // This tests that we don't infinite loop. - TestRecursiveOneof.getDefaultInstance().hashCode(); + int unused = TestRecursiveOneof.getDefaultInstance().hashCode(); } } diff --git a/python/google/protobuf/internal/descriptor_test.py b/python/google/protobuf/internal/descriptor_test.py index 39a19c2c2a310..3af2bdcf566ca 100644 --- a/python/google/protobuf/internal/descriptor_test.py +++ b/python/google/protobuf/internal/descriptor_test.py @@ -118,6 +118,22 @@ def setUp(self): def GetDescriptorPool(self): return symbol_database.Default().pool + def testMissingPackage(self): + file_proto = descriptor_pb2.FileDescriptorProto( + name='some/filename/some.proto') + serialized = file_proto.SerializeToString() + pool = descriptor_pool.DescriptorPool() + file_descriptor = pool.AddSerializedFile(serialized) + self.assertEqual('', file_descriptor.package) + + def testEmptyPackage(self): + file_proto = descriptor_pb2.FileDescriptorProto( + name='some/filename/some.proto', package='') + serialized = file_proto.SerializeToString() + pool = descriptor_pool.DescriptorPool() + file_descriptor = pool.AddSerializedFile(serialized) + self.assertEqual('', file_descriptor.package) + def testFindMethodByName(self): service_descriptor = (unittest_custom_options_pb2. TestServiceWithCustomOptions.DESCRIPTOR) diff --git a/python/google/protobuf/internal/message_test.py b/python/google/protobuf/internal/message_test.py index 3671c31a66d9a..624dbdb1524f9 100644 --- a/python/google/protobuf/internal/message_test.py +++ b/python/google/protobuf/internal/message_test.py @@ -2455,6 +2455,26 @@ def testSurrogatesInPython3(self): with self.assertRaises(ValueError): unittest_proto3_arena_pb2.TestAllTypes(optional_string=u'\ud801\ud801') + def testCrashNullAA(self): + self.assertEqual( + unittest_proto3_arena_pb2.TestAllTypes.NestedMessage(), + unittest_proto3_arena_pb2.TestAllTypes.NestedMessage()) + + def testCrashNullAB(self): + self.assertEqual( + unittest_proto3_arena_pb2.TestAllTypes.NestedMessage(), + unittest_proto3_arena_pb2.TestAllTypes().optional_nested_message) + + def testCrashNullBA(self): + self.assertEqual( + unittest_proto3_arena_pb2.TestAllTypes().optional_nested_message, + unittest_proto3_arena_pb2.TestAllTypes.NestedMessage()) + + def testCrashNullBB(self): + self.assertEqual( + unittest_proto3_arena_pb2.TestAllTypes().optional_nested_message, + unittest_proto3_arena_pb2.TestAllTypes().optional_nested_message) + diff --git a/src/google/protobuf/arena.cc b/src/google/protobuf/arena.cc index 9200cb2d043ab..0c1bcf922735c 100644 --- a/src/google/protobuf/arena.cc +++ b/src/google/protobuf/arena.cc @@ -382,7 +382,7 @@ void ThreadSafeArena::Init() { void ThreadSafeArena::SetInitialBlock(void* mem, size_t size) { SerialArena* serial = SerialArena::New({mem, size}, &thread_cache(), arena_stats_.MutableStats()); - serial->set_next(NULL); + serial->set_next(nullptr); threads_.store(serial, std::memory_order_relaxed); CacheSerialArena(serial); } diff --git a/src/google/protobuf/arena.h b/src/google/protobuf/arena.h index b6297808225f7..59e02e0963d22 100644 --- a/src/google/protobuf/arena.h +++ b/src/google/protobuf/arena.h @@ -120,7 +120,7 @@ struct ArenaOptions { // here. size_t max_block_size; - // An initial block of memory for the arena to use, or NULL for none. If + // An initial block of memory for the arena to use, or nullptr for none. If // provided, the block must live at least as long as the arena itself. The // creator of the Arena retains ownership of the block after the Arena is // destroyed. @@ -143,7 +143,7 @@ struct ArenaOptions { ArenaOptions() : start_block_size(internal::AllocationPolicy::kDefaultStartBlockSize), max_block_size(internal::AllocationPolicy::kDefaultMaxBlockSize), - initial_block(NULL), + initial_block(nullptr), initial_block_size(0), block_alloc(nullptr), block_dealloc(nullptr), @@ -180,7 +180,7 @@ struct ArenaOptions { #if PROTOBUF_RTTI #define RTTI_TYPE_ID(type) (&typeid(type)) #else -#define RTTI_TYPE_ID(type) (NULL) +#define RTTI_TYPE_ID(type) (nullptr) #endif // Arena allocator. Arena allocation replaces ordinary (heap-based) allocation @@ -210,7 +210,7 @@ struct ArenaOptions { // with `args` (without `arena`), called when a T is allocated on the heap; // and a constructor callable with `Arena* arena, Args&&... args`, called when // a T is allocated on an arena. If the second constructor is called with a -// NULL arena pointer, it must be equivalent to invoking the first +// null arena pointer, it must be equivalent to invoking the first // (`args`-only) constructor. // // - The type T must have a particular type trait: a nested type @@ -220,7 +220,7 @@ struct ArenaOptions { // // - The type T *may* have the type trait |DestructorSkippable_|. If this type // trait is present in the type, then its destructor will not be called if and -// only if it was passed a non-NULL arena pointer. If this type trait is not +// only if it was passed a non-null arena pointer. If this type trait is not // present on the type, then its destructor is always called when the // containing arena is destroyed. // @@ -263,9 +263,9 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final { void Init(const ArenaOptions&) {} // API to create proto2 message objects on the arena. If the arena passed in - // is NULL, then a heap allocated object is returned. Type T must be a message - // defined in a .proto file with cc_enable_arenas set to true, otherwise a - // compilation error will occur. + // is nullptr, then a heap allocated object is returned. Type T must be a + // message defined in a .proto file with cc_enable_arenas set to true, + // otherwise a compilation error will occur. // // RepeatedField and RepeatedPtrField may also be instantiated directly on an // arena with this method. @@ -342,7 +342,7 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final { "CreateArray requires a trivially destructible type"); GOOGLE_CHECK_LE(num_elements, std::numeric_limits::max() / sizeof(T)) << "Requested size is too large to fit into size_t."; - if (arena == NULL) { + if (arena == nullptr) { return static_cast(::operator new[](num_elements * sizeof(T))); } else { return arena->CreateInternalRawArray(num_elements); @@ -386,7 +386,7 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final { // arena-allocated memory. template PROTOBUF_ALWAYS_INLINE void OwnDestructor(T* object) { - if (object != NULL) { + if (object != nullptr) { impl_.AddCleanup(object, &internal::cleanup::arena_destruct_object); } } @@ -401,9 +401,9 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final { } // Retrieves the arena associated with |value| if |value| is an arena-capable - // message, or NULL otherwise. If possible, the call resolves at compile time. - // Note that we can often devirtualize calls to `value->GetArena()` so usually - // calling this method is unnecessary. + // message, or nullptr otherwise. If possible, the call resolves at compile + // time. Note that we can often devirtualize calls to `value->GetArena()` so + // usually calling this method is unnecessary. template PROTOBUF_ALWAYS_INLINE static Arena* GetArena(const T* value) { return GetArenaInternal(value); @@ -568,7 +568,7 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final { static_assert( InternalHelper::is_arena_constructable::value, "CreateMessage can only construct types that are ArenaConstructable"); - if (arena == NULL) { + if (arena == nullptr) { return new T(nullptr, static_cast(args)...); } else { return arena->DoCreateMessage(static_cast(args)...); @@ -583,7 +583,7 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final { static_assert( InternalHelper::is_arena_constructable::value, "CreateMessage can only construct types that are ArenaConstructable"); - if (arena == NULL) { + if (arena == nullptr) { // Generated arena constructor T(Arena*) is protected. Call via // InternalHelper. return InternalHelper::New(); @@ -730,13 +730,13 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final { // using the virtual destructor instead. template PROTOBUF_ALWAYS_INLINE void OwnInternal(T* object, std::true_type) { - if (object != NULL) { + if (object != nullptr) { impl_.AddCleanup(object, &internal::arena_delete_object); } } template PROTOBUF_ALWAYS_INLINE void OwnInternal(T* object, std::false_type) { - if (object != NULL) { + if (object != nullptr) { impl_.AddCleanup(object, &internal::arena_delete_object); } } diff --git a/src/google/protobuf/arena_unittest.cc b/src/google/protobuf/arena_unittest.cc index 6c88505d8d4b4..0987c9929edf2 100644 --- a/src/google/protobuf/arena_unittest.cc +++ b/src/google/protobuf/arena_unittest.cc @@ -84,10 +84,10 @@ class Notifier { class SimpleDataType { public: - SimpleDataType() : notifier_(NULL) {} + SimpleDataType() : notifier_(nullptr) {} void SetNotifier(Notifier* notifier) { notifier_ = notifier; } virtual ~SimpleDataType() { - if (notifier_ != NULL) { + if (notifier_ != nullptr) { notifier_->Notify(); } }; @@ -171,17 +171,17 @@ TEST(ArenaTest, DestructorSkippable) { TEST(ArenaTest, BasicCreate) { Arena arena; - EXPECT_TRUE(Arena::Create(&arena) != NULL); - EXPECT_TRUE(Arena::Create(&arena) != NULL); - EXPECT_TRUE(Arena::Create(&arena) != NULL); - EXPECT_TRUE(Arena::Create(&arena) != NULL); - EXPECT_TRUE(Arena::Create(&arena) != NULL); + EXPECT_TRUE(Arena::Create(&arena) != nullptr); + EXPECT_TRUE(Arena::Create(&arena) != nullptr); + EXPECT_TRUE(Arena::Create(&arena) != nullptr); + EXPECT_TRUE(Arena::Create(&arena) != nullptr); + EXPECT_TRUE(Arena::Create(&arena) != nullptr); arena.Own(new int32_t); arena.Own(new int64_t); arena.Own(new float); arena.Own(new double); arena.Own(new std::string); - arena.Own(NULL); + arena.Own(nullptr); Notifier notifier; SimpleDataType* data = Arena::Create(&arena); data->SetNotifier(¬ifier); @@ -196,7 +196,7 @@ TEST(ArenaTest, CreateAndConstCopy) { Arena arena; const std::string s("foo"); const std::string* s_copy = Arena::Create(&arena, s); - EXPECT_TRUE(s_copy != NULL); + EXPECT_TRUE(s_copy != nullptr); EXPECT_EQ("foo", s); EXPECT_EQ("foo", *s_copy); } @@ -205,7 +205,7 @@ TEST(ArenaTest, CreateAndNonConstCopy) { Arena arena; std::string s("foo"); const std::string* s_copy = Arena::Create(&arena, s); - EXPECT_TRUE(s_copy != NULL); + EXPECT_TRUE(s_copy != nullptr); EXPECT_EQ("foo", s); EXPECT_EQ("foo", *s_copy); } @@ -214,7 +214,7 @@ TEST(ArenaTest, CreateAndMove) { Arena arena; std::string s("foo"); const std::string* s_move = Arena::Create(&arena, std::move(s)); - EXPECT_TRUE(s_move != NULL); + EXPECT_TRUE(s_move != nullptr); EXPECT_TRUE(s.empty()); // NOLINT EXPECT_EQ("foo", *s_move); } @@ -226,7 +226,7 @@ TEST(ArenaTest, CreateWithFourConstructorArguments) { const MustBeConstructedWithOneThroughFour* new_object = Arena::Create(&arena, 1, "2", three, &four); - EXPECT_TRUE(new_object != NULL); + EXPECT_TRUE(new_object != nullptr); ASSERT_EQ(1, new_object->one_); ASSERT_STREQ("2", new_object->two_); ASSERT_EQ("3", new_object->three_); @@ -242,7 +242,7 @@ TEST(ArenaTest, CreateWithEightConstructorArguments) { const MustBeConstructedWithOneThroughEight* new_object = Arena::Create( &arena, 1, "2", three, &four, 5, "6", seven, eight); - EXPECT_TRUE(new_object != NULL); + EXPECT_TRUE(new_object != nullptr); ASSERT_EQ(1, new_object->one_); ASSERT_STREQ("2", new_object->two_); ASSERT_EQ("3", new_object->three_); @@ -504,7 +504,7 @@ TEST(ArenaTest, ReleaseMessage) { TestAllTypes::NestedMessage* released_null = arena_message->release_optional_nested_message(); - EXPECT_EQ(NULL, released_null); + EXPECT_EQ(nullptr, released_null); } TEST(ArenaTest, SetAllocatedString) { @@ -605,8 +605,8 @@ TEST(ArenaTest, SwapBetweenArenaAndNonArenaUsingReflection) { } TEST(ArenaTest, ReleaseFromArenaMessageMakesCopy) { - TestAllTypes::NestedMessage* nested_msg = NULL; - std::string* nested_string = NULL; + TestAllTypes::NestedMessage* nested_msg = nullptr; + std::string* nested_string = nullptr; { Arena arena; TestAllTypes* arena_message = Arena::CreateMessage(&arena); @@ -623,7 +623,7 @@ TEST(ArenaTest, ReleaseFromArenaMessageMakesCopy) { #if PROTOBUF_RTTI TEST(ArenaTest, ReleaseFromArenaMessageUsingReflectionMakesCopy) { - TestAllTypes::NestedMessage* nested_msg = NULL; + TestAllTypes::NestedMessage* nested_msg = nullptr; // Note: no string: reflection API only supports releasing submessages. { Arena arena; @@ -1098,7 +1098,7 @@ TEST(ArenaTest, ArenaOneofReflection) { EXPECT_TRUE(refl->HasOneof(*message, oneof)); submsg = refl->ReleaseMessage(message, msg_field); EXPECT_FALSE(refl->HasOneof(*message, oneof)); - EXPECT_TRUE(submsg->GetArena() == NULL); + EXPECT_TRUE(submsg->GetArena() == nullptr); delete submsg; } @@ -1111,7 +1111,7 @@ void TestSwapRepeatedField(Arena* arena1, Arena* arena2) { TestAllTypes* t = Arena::CreateMessage(arena1); t->set_optional_string("field1"); t->set_optional_int32(i); - if (arena1 != NULL) { + if (arena1 != nullptr) { field1.UnsafeArenaAddAllocated(t); } else { field1.AddAllocated(t); @@ -1121,7 +1121,7 @@ void TestSwapRepeatedField(Arena* arena1, Arena* arena2) { TestAllTypes* t = Arena::CreateMessage(arena2); t->set_optional_string("field2"); t->set_optional_int32(i); - if (arena2 != NULL) { + if (arena2 != nullptr) { field2.UnsafeArenaAddAllocated(t); } else { field2.AddAllocated(t); @@ -1154,12 +1154,12 @@ TEST(ArenaTest, SwapRepeatedFieldWithDifferentArenas) { TEST(ArenaTest, SwapRepeatedFieldWithNoArenaOnRightHandSide) { Arena arena; - TestSwapRepeatedField(&arena, NULL); + TestSwapRepeatedField(&arena, nullptr); } TEST(ArenaTest, SwapRepeatedFieldWithNoArenaOnLeftHandSide) { Arena arena; - TestSwapRepeatedField(NULL, &arena); + TestSwapRepeatedField(nullptr, &arena); } TEST(ArenaTest, ExtensionsOnArena) { @@ -1218,11 +1218,11 @@ TEST(ArenaTest, RepeatedFieldOnArena) { TestAllTypes* extracted_messages[5]; // ExtractSubrange should copy to the heap. repeated_message.ExtractSubrange(0, 5, extracted_messages); - EXPECT_EQ(NULL, extracted_messages[0]->GetArena()); + EXPECT_EQ(nullptr, extracted_messages[0]->GetArena()); // We need to free the heap-allocated messages to prevent a leak. for (int i = 0; i < 5; i++) { delete extracted_messages[i]; - extracted_messages[i] = NULL; + extracted_messages[i] = nullptr; } } @@ -1342,7 +1342,7 @@ TEST(ArenaTest, MessageLiteOnArena) { { MessageLite* generic_message = prototype->New(&arena); - EXPECT_TRUE(generic_message != NULL); + EXPECT_TRUE(generic_message != nullptr); EXPECT_EQ(&arena, generic_message->GetArena()); EXPECT_TRUE(generic_message->ParseFromString(serialized)); TestAllTypes* deserialized = static_cast(generic_message); @@ -1463,8 +1463,8 @@ TEST(ArenaTest, GetArenaShouldReturnTheArenaForArenaAllocatedMessages) { TEST(ArenaTest, GetArenaShouldReturnNullForNonArenaAllocatedMessages) { ArenaMessage message; const ArenaMessage* const_pointer_to_message = &message; - EXPECT_EQ(NULL, Arena::GetArena(&message)); - EXPECT_EQ(NULL, Arena::GetArena(const_pointer_to_message)); + EXPECT_EQ(nullptr, Arena::GetArena(&message)); + EXPECT_EQ(nullptr, Arena::GetArena(const_pointer_to_message)); } TEST(ArenaTest, GetArenaShouldReturnNullForNonArenaCompatibleTypes) { diff --git a/src/google/protobuf/compiler/command_line_interface.cc b/src/google/protobuf/compiler/command_line_interface.cc index a688bcfb0623d..0a0c7e368b4f8 100644 --- a/src/google/protobuf/compiler/command_line_interface.cc +++ b/src/google/protobuf/compiler/command_line_interface.cc @@ -1680,8 +1680,7 @@ CommandLineInterface::InterpretArgument(const std::string& name, })) { case google::protobuf::io::win32::ExpandWildcardsResult::kSuccess: break; - case google::protobuf::io::win32::ExpandWildcardsResult:: - kErrorNoMatchingFile: + case google::protobuf::io::win32::ExpandWildcardsResult::kErrorNoMatchingFile: // Path does not exist, is not a file, or it's longer than MAX_PATH and // long path handling is disabled. std::cerr << "Invalid file name pattern or missing input file \"" diff --git a/src/google/protobuf/compiler/cpp/helpers.cc b/src/google/protobuf/compiler/cpp/helpers.cc index a8c5899180dff..4939aa533f9b9 100644 --- a/src/google/protobuf/compiler/cpp/helpers.cc +++ b/src/google/protobuf/compiler/cpp/helpers.cc @@ -925,6 +925,13 @@ bool HasLazyFields(const FileDescriptor* file, const Options& options, bool ShouldSplit(const Descriptor*, const Options&) { return false; } bool ShouldSplit(const FieldDescriptor*, const Options&) { return false; } +bool ShouldForceAllocationOnConstruction(const Descriptor* desc, + const Options& options) { + (void)desc; + (void)options; + return false; +} + static bool HasRepeatedFields(const Descriptor* descriptor) { for (int i = 0; i < descriptor->field_count(); ++i) { if (descriptor->field(i)->label() == FieldDescriptor::LABEL_REPEATED) { diff --git a/src/google/protobuf/compiler/cpp/helpers.h b/src/google/protobuf/compiler/cpp/helpers.h index 6befbaa0cb0e8..21a488e2a5f2f 100644 --- a/src/google/protobuf/compiler/cpp/helpers.h +++ b/src/google/protobuf/compiler/cpp/helpers.h @@ -379,6 +379,11 @@ bool ShouldSplit(const Descriptor* desc, const Options& options); // Is the given field being split out? bool ShouldSplit(const FieldDescriptor* field, const Options& options); +// Should we generate code that force creating an allocation in the constructor +// of the given message? +bool ShouldForceAllocationOnConstruction(const Descriptor* desc, + const Options& options); + inline bool IsFieldUsed(const FieldDescriptor* /* field */, const Options& /* options */) { return true; diff --git a/src/google/protobuf/compiler/cpp/message.cc b/src/google/protobuf/compiler/cpp/message.cc index 04bb5d1492984..3ca53154dd2da 100644 --- a/src/google/protobuf/compiler/cpp/message.cc +++ b/src/google/protobuf/compiler/cpp/message.cc @@ -1269,9 +1269,9 @@ void MessageGenerator::GenerateFieldAccessorDefinitions(io::Printer* printer) { GenerateSingularFieldHasBits(field, format); } - if (!IsCrossFileMaybeMap(field)) { - GenerateFieldClear(field, true, format); - } + if (!IsCrossFileMaybeMap(field)) { + GenerateFieldClear(field, true, format); + } // Generate type-specific accessors. if (!IsFieldStripped(field, options_)) { @@ -2483,6 +2483,13 @@ void MessageGenerator::GenerateSharedConstructorCode(io::Printer* printer) { field_generators_.get(field).GenerateConstructorCode(printer); } + if (ShouldForceAllocationOnConstruction(descriptor_, options_)) { + format( + "#ifdef PROTOBUF_FORCE_ALLOCATION_ON_CONSTRUCTION\n" + "$mutable_unknown_fields$;\n" + "#endif // PROTOBUF_FORCE_ALLOCATION_ON_CONSTRUCTION\n"); + } + for (auto oneof : OneOfRange(descriptor_)) { format("clear_has_$1$();\n", oneof->name()); } @@ -2722,6 +2729,13 @@ void MessageGenerator::GenerateCopyConstructorBody(io::Printer* printer) const { " static_cast(reinterpret_cast(&$last$) -\n" " reinterpret_cast(&$first$)) + sizeof($last$));\n"; + if (ShouldForceAllocationOnConstruction(descriptor_, options_)) { + format( + "#ifdef PROTOBUF_FORCE_ALLOCATION_ON_CONSTRUCTION\n" + "$mutable_unknown_fields$;\n" + "#endif // PROTOBUF_FORCE_ALLOCATION_ON_CONSTRUCTION\n"); + } + for (size_t i = 0; i < optimized_order_.size(); ++i) { const FieldDescriptor* field = optimized_order_[i]; if (ShouldSplit(field, options_)) { diff --git a/src/google/protobuf/compiler/subprocess.cc b/src/google/protobuf/compiler/subprocess.cc index 6f547db79550f..6faab05137644 100644 --- a/src/google/protobuf/compiler/subprocess.cc +++ b/src/google/protobuf/compiler/subprocess.cc @@ -46,8 +46,8 @@ #include #include #include -#include #include +#include namespace google { namespace protobuf { diff --git a/src/google/protobuf/io/coded_stream.h b/src/google/protobuf/io/coded_stream.h index a8b5eb57be73b..e428f55b4b081 100644 --- a/src/google/protobuf/io/coded_stream.h +++ b/src/google/protobuf/io/coded_stream.h @@ -680,7 +680,7 @@ class PROTOBUF_EXPORT EpsCopyOutputStream { if (PROTOBUF_PREDICT_FALSE(end_ - ptr < size)) { return WriteRawFallback(data, size, ptr); } - std::memcpy(ptr, data, size); + std::memcpy(ptr, data, static_cast(size)); return ptr + size; } // Writes the buffer specified by data, size to the stream. Possibly by @@ -1776,7 +1776,7 @@ inline void CodedOutputStream::WriteRawMaybeAliased(const void* data, inline uint8_t* CodedOutputStream::WriteRawToArray(const void* data, int size, uint8_t* target) { - memcpy(target, data, size); + memcpy(target, data, static_cast(size)); return target + size; } diff --git a/src/google/protobuf/map.h b/src/google/protobuf/map.h index a0e1b610a833c..d0aa01081624a 100644 --- a/src/google/protobuf/map.h +++ b/src/google/protobuf/map.h @@ -379,6 +379,7 @@ class Map { public: using key_type = Key; using mapped_type = T; + using init_type = std::pair; using value_type = MapPair; using pointer = value_type*; @@ -421,6 +422,22 @@ class Map { ~Map() {} private: + template + struct SameAsElementReference + : std::is_same::type>::type, + typename std::remove_cv< + typename std::remove_reference

::type>::type> {}; + + template + using RequiresInsertable = + typename std::enable_if::value || + SameAsElementReference

::value, + int>::type; + template + using RequiresNotInit = + typename std::enable_if::value, int>::type; + using Allocator = internal::MapAllocator; // InnerMap is a generic hash-based map. It doesn't contain any @@ -1270,7 +1287,6 @@ class Map { const_iterator cbegin() const { return begin(); } const_iterator cend() const { return end(); } - // Capacity size_type size() const { return elements_.size(); } bool empty() const { return size() == 0; } @@ -1351,15 +1367,17 @@ class Map { elements_.try_emplace(std::forward(k), std::forward(args)...); return std::pair(iterator(p.first), p.second); } - std::pair insert(const value_type& value) { - return try_emplace(value.first, value.second); + std::pair insert(init_type&& value) { + return try_emplace(std::move(value.first), std::move(value.second)); } - std::pair insert(value_type&& value) { - return try_emplace(value.first, std::move(value.second)); + template = 0> + std::pair insert(P&& value) { + return try_emplace(std::forward

(value).first, + std::forward

(value).second); } template std::pair emplace(Args&&... args) { - return insert(value_type(std::forward(args)...)); + return EmplaceInternal(Rank0{}, std::forward(args)...); } template void insert(InputIt first, InputIt last) { @@ -1368,7 +1386,12 @@ class Map { try_emplace(pair.first, pair.second); } } - void insert(std::initializer_list values) { + void insert(std::initializer_list values) { + insert(values.begin(), values.end()); + } + template = 0, + RequiresInsertable = 0> + void insert(std::initializer_list

values) { insert(values.begin(), values.end()); } @@ -1429,6 +1452,23 @@ class Map { } private: + struct Rank1 {}; + struct Rank0 : Rank1 {}; + + // We try to construct `init_type` from `Args` with a fall back to + // `value_type`. The latter is less desired as it unconditionally makes a copy + // of `value_type::first`. + template + auto EmplaceInternal(Rank0, Args&&... args) -> + typename std::enable_if::value, + std::pair>::type { + return insert(init_type(std::forward(args)...)); + } + template + std::pair EmplaceInternal(Rank1, Args&&... args) { + return insert(value_type(std::forward(args)...)); + } + Arena* arena() const { return elements_.arena(); } InnerMap elements_; diff --git a/src/google/protobuf/map_test.inc b/src/google/protobuf/map_test.inc index 550d986af69c0..041ac824d47ac 100644 --- a/src/google/protobuf/map_test.inc +++ b/src/google/protobuf/map_test.inc @@ -737,6 +737,47 @@ TEST_F(MapImplTest, InsertSingleRValue) { EXPECT_FALSE(result2.second); } +TEST_F(MapImplTest, InsertSingleBraceInitList) { + int32_t key = 0; + int32_t value1 = 100; + int32_t value2 = 101; + + // Insert a non-existing key. + auto result1 = map_.insert({key, value1}); + ExpectSingleElement(key, value1); + + auto it1 = result1.first; + EXPECT_EQ(key, it1->first); + EXPECT_EQ(value1, it1->second); + EXPECT_TRUE(result1.second); + + // Insert an existing key. + auto result2 = map_.insert({key, value2}); + ExpectSingleElement(key, value1); + + auto it2 = result2.first; + EXPECT_TRUE(it1 == it2); + EXPECT_FALSE(result2.second); +} + +TEST_F(MapImplTest, InsertSingleBraceInitListTypeMismatch) { + int32_t key = 0; + int32_t value1 = 100; + int32_t value2 = 101; + Map m; + + // Insert a non-existing key. + auto result1 = m.insert({key, value1}); + EXPECT_TRUE(result1.second); + + // Insert an existing key. + auto result2 = m.insert({key, value2}); + EXPECT_FALSE(result2.second); + + EXPECT_TRUE(result1.first == result2.first); +} + + TEST_F(MapImplTest, TryEmplace) { using ::testing::Pair; using ::testing::UnorderedElementsAre; @@ -769,6 +810,20 @@ TEST_F(MapImplTest, Emplace) { m, UnorderedElementsAre(Pair(1, "one"), Pair(2, "two"), Pair(42, "aaa"))); } +TEST_F(MapImplTest, EmplaceKeyOnly) { + using ::testing::Pair; + using ::testing::UnorderedElementsAre; + + Map m; + + m.emplace(1); + EXPECT_EQ(m.size(), 1); + + const int32_t key = 42; + m.emplace(key); + EXPECT_THAT(m, UnorderedElementsAre(Pair(1, ""), Pair(42, ""))); +} + struct CountedInstance { CountedInstance() { ++num_created; } CountedInstance(const CountedInstance&) : CountedInstance() {} diff --git a/src/google/protobuf/port_def.inc b/src/google/protobuf/port_def.inc index f0aa3deb201a8..2e861d28e5843 100644 --- a/src/google/protobuf/port_def.inc +++ b/src/google/protobuf/port_def.inc @@ -191,6 +191,7 @@ #define PROTOBUF_FUTURE_REMOVE_CLEARED_API 1 // Used to escape C++20 keywords. +// TODO(mkruskal): ping b/238664698 when this lands in opensource. // Owner: mkruskal@ #define PROTOBUF_FUTURE_CPP20_KEYWORDS 1 #else @@ -559,6 +560,10 @@ #error PROTOBUF_FORCE_COPY_DEFAULT_STRING was previously defined #endif +#ifdef PROTOBUF_FORCE_ALLOCATION_ON_CONSTRUCTION +#error PROTOBUF_FORCE_ALLOCATION_ON_CONSTRUCTION was previously defined +#endif + #ifdef PROTOBUF_FALLTHROUGH_INTENDED #error PROTOBUF_FALLTHROUGH_INTENDED was previously defined #endif diff --git a/src/google/protobuf/port_undef.inc b/src/google/protobuf/port_undef.inc index 5732b197c4cc6..4ad9d33872635 100644 --- a/src/google/protobuf/port_undef.inc +++ b/src/google/protobuf/port_undef.inc @@ -75,6 +75,7 @@ #undef PROTOBUF_FORCE_RESET_IN_CLEAR #undef PROTOBUF_FUZZ_MESSAGE_SPACE_USED_LONG #undef PROTOBUF_FORCE_COPY_DEFAULT_STRING +#undef PROTOBUF_FORCE_ALLOCATION_ON_CONSTRUCTION #undef PROTOBUF_NAMESPACE_OPEN #undef PROTOBUF_NAMESPACE_CLOSE #undef PROTOBUF_UNUSED diff --git a/src/google/protobuf/repeated_field.h b/src/google/protobuf/repeated_field.h index 3fbe4995cd0af..e504274778746 100644 --- a/src/google/protobuf/repeated_field.h +++ b/src/google/protobuf/repeated_field.h @@ -311,6 +311,7 @@ class RepeatedField final { // This is public due to it being called by generated code. inline void InternalSwap(RepeatedField* other); + private: template friend class Arena::InternalHelper; @@ -338,7 +339,7 @@ class RepeatedField final { // current_size_. This function is intended to be the only place where // current_size_ is modified. inline int ExchangeCurrentSize(int new_size) { - int prev_size = current_size_; + const int prev_size = current_size_; current_size_ = new_size; return prev_size; } @@ -355,6 +356,7 @@ class RepeatedField final { } }; + // If total_size_ == 0 this points to an Arena otherwise it points to the // elements member of a Rep struct. Using this invariant allows the storage of // the arena pointer without an extra allocation in the constructor. @@ -471,7 +473,7 @@ class RepeatedField final { void Add(Element val) { if (index_ == capacity_) { - repeated_field_->ExchangeCurrentSize(index_); + repeated_field_->current_size_ = index_; repeated_field_->Reserve(index_ + 1); capacity_ = repeated_field_->total_size_; buffer_ = repeated_field_->unsafe_elements(); diff --git a/src/google/protobuf/util/json_util.h b/src/google/protobuf/util/json_util.h index 4f4594e2f9c76..8fbab5a46a3d0 100644 --- a/src/google/protobuf/util/json_util.h +++ b/src/google/protobuf/util/json_util.h @@ -93,6 +93,9 @@ typedef JsonPrintOptions JsonOptions; // Converts from protobuf message to JSON and appends it to |output|. This is a // simple wrapper of BinaryToJsonString(). It will use the DescriptorPool of the // passed-in message to resolve Any types. +// +// Please note that non-OK statuses are not a stable output of this API and +// subject to change without notice. PROTOBUF_EXPORT util::Status MessageToJsonString(const Message& message, std::string* output, const JsonOptions& options); @@ -105,6 +108,9 @@ inline util::Status MessageToJsonString(const Message& message, // Converts from JSON to protobuf message. This is a simple wrapper of // JsonStringToBinary(). It will use the DescriptorPool of the passed-in // message to resolve Any types. +// +// Please note that non-OK statuses are not a stable output of this API and +// subject to change without notice. PROTOBUF_EXPORT util::Status JsonStringToMessage( StringPiece input, Message* message, const JsonParseOptions& options); @@ -119,6 +125,9 @@ inline util::Status JsonStringToMessage(StringPiece input, // 2. input is not valid protobuf wire format, or conflicts with the type // information returned by TypeResolver. // Note that unknown fields will be discarded silently. +// +// Please note that non-OK statuses are not a stable output of this API and +// subject to change without notice. PROTOBUF_EXPORT util::Status BinaryToJsonStream( TypeResolver* resolver, const std::string& type_url, io::ZeroCopyInputStream* binary_input, @@ -150,6 +159,9 @@ inline util::Status BinaryToJsonString(TypeResolver* resolver, // 1. TypeResolver fails to resolve a type. // 2. input is not valid JSON format, or conflicts with the type // information returned by TypeResolver. +// +// Please note that non-OK statuses are not a stable output of this API and +// subject to change without notice. PROTOBUF_EXPORT util::Status JsonToBinaryStream( TypeResolver* resolver, const std::string& type_url, io::ZeroCopyInputStream* json_input, diff --git a/src/google/protobuf/util/json_util_test.cc b/src/google/protobuf/util/json_util_test.cc index 1ff638b66ece3..1c7207217e10c 100644 --- a/src/google/protobuf/util/json_util_test.cc +++ b/src/google/protobuf/util/json_util_test.cc @@ -74,6 +74,8 @@ using ::proto3::TestMessage; using ::proto3::TestOneof; using ::proto3::TestWrapper; using ::proto_util_converter::testing::MapIn; +using ::testing::ElementsAre; +using ::testing::SizeIs; // TODO(b/234474291): Use the gtest versions once that's available in OSS. MATCHER_P(IsOkAndHolds, inner, @@ -301,9 +303,8 @@ TEST_P(JsonTest, TestAlwaysPrintEnumsAsInts) { ASSERT_OK(parsed); EXPECT_EQ(parsed->enum_value(), proto3::BAR); - EXPECT_EQ(parsed->repeated_enum_value_size(), 2); - EXPECT_EQ(parsed->repeated_enum_value(0), proto3::FOO); - EXPECT_EQ(parsed->repeated_enum_value(1), proto3::BAR); + EXPECT_THAT(parsed->repeated_enum_value(), + ElementsAre(proto3::FOO, proto3::BAR)); } TEST_P(JsonTest, TestPrintEnumsAsIntsWithDefaultValue) { @@ -398,31 +399,14 @@ TEST_P(JsonTest, ParseMessage) { EXPECT_EQ(m->enum_value(), proto3::EnumType::BAR); EXPECT_EQ(m->message_value().value(), 2048); - ASSERT_EQ(m->repeated_bool_value_size(), 1); - EXPECT_TRUE(m->repeated_bool_value(0)); + EXPECT_THAT(m->repeated_bool_value(), ElementsAre(true)); + EXPECT_THAT(m->repeated_int32_value(), ElementsAre(0, -42)); + EXPECT_THAT(m->repeated_uint64_value(), ElementsAre(1, 2)); + EXPECT_THAT(m->repeated_double_value(), ElementsAre(1.5, -2)); + EXPECT_THAT(m->repeated_string_value(), ElementsAre("foo", "bar ", "")); + EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::BAR, proto3::FOO)); - ASSERT_EQ(m->repeated_int32_value_size(), 2); - EXPECT_EQ(m->repeated_int32_value(0), 0); - EXPECT_EQ(m->repeated_int32_value(1), -42); - - ASSERT_EQ(m->repeated_uint64_value_size(), 2); - EXPECT_EQ(m->repeated_uint64_value(0), 1); - EXPECT_EQ(m->repeated_uint64_value(1), 2); - - ASSERT_EQ(m->repeated_double_value_size(), 2); - EXPECT_EQ(m->repeated_double_value(0), 1.5); - EXPECT_EQ(m->repeated_double_value(1), -2); - - ASSERT_EQ(m->repeated_string_value_size(), 3); - EXPECT_EQ(m->repeated_string_value(0), "foo"); - EXPECT_EQ(m->repeated_string_value(1), "bar "); - EXPECT_EQ(m->repeated_string_value(2), ""); - - ASSERT_EQ(m->repeated_enum_value_size(), 2); - EXPECT_EQ(m->repeated_enum_value(0), proto3::EnumType::BAR); - EXPECT_EQ(m->repeated_enum_value(1), proto3::EnumType::FOO); - - ASSERT_EQ(m->repeated_message_value_size(), 2); + ASSERT_THAT(m->repeated_message_value(), SizeIs(2)); EXPECT_EQ(m->repeated_message_value(0).value(), 40); EXPECT_EQ(m->repeated_message_value(1).value(), 96); @@ -445,19 +429,9 @@ TEST_P(JsonTest, CurseOfAtob) { } )json"); ASSERT_OK(m); - EXPECT_EQ(m->repeated_bool_value_size(), 10); - - EXPECT_FALSE(m->repeated_bool_value(0)); - EXPECT_FALSE(m->repeated_bool_value(2)); - EXPECT_FALSE(m->repeated_bool_value(4)); - EXPECT_FALSE(m->repeated_bool_value(6)); - EXPECT_FALSE(m->repeated_bool_value(8)); - - EXPECT_TRUE(m->repeated_bool_value(1)); - EXPECT_TRUE(m->repeated_bool_value(3)); - EXPECT_TRUE(m->repeated_bool_value(5)); - EXPECT_TRUE(m->repeated_bool_value(7)); - EXPECT_TRUE(m->repeated_bool_value(9)); + EXPECT_THAT(m->repeated_bool_value(), + ElementsAre(false, true, false, true, false, true, false, true, + false, true)); } TEST_P(JsonTest, ParseLegacySingleRepeatedField) { @@ -469,16 +443,11 @@ TEST_P(JsonTest, ParseLegacySingleRepeatedField) { })json"); ASSERT_OK(m); - ASSERT_EQ(m->repeated_int32_value_size(), 1); - EXPECT_EQ(m->repeated_int32_value(0), 1997); - - ASSERT_EQ(m->repeated_string_value_size(), 1); - EXPECT_EQ(m->repeated_string_value(0), "oh no"); + EXPECT_THAT(m->repeated_int32_value(), ElementsAre(1997)); + EXPECT_THAT(m->repeated_string_value(), ElementsAre("oh no")); + EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::EnumType::BAR)); - ASSERT_EQ(m->repeated_enum_value_size(), 1); - EXPECT_EQ(m->repeated_enum_value(0), proto3::EnumType::BAR); - - ASSERT_EQ(m->repeated_message_value_size(), 1); + ASSERT_THAT(m->repeated_message_value(), SizeIs(1)); EXPECT_EQ(m->repeated_message_value(0).value(), -1); EXPECT_THAT(ToJson(*m), @@ -499,6 +468,13 @@ TEST_P(JsonTest, ParseMap) { EXPECT_EQ(other->DebugString(), message.DebugString()); } +TEST_P(JsonTest, RepeatedMapKey) { + EXPECT_THAT(ToProto(R"json({ + "twiceKey": 0, + "twiceKey": 1 + })json"), StatusIs(util::StatusCode::kInvalidArgument)); +} + TEST_P(JsonTest, ParsePrimitiveMapIn) { MapIn message; JsonPrintOptions print_options; @@ -535,10 +511,43 @@ TEST_P(JsonTest, ParseOverOneof) { EXPECT_EQ(m.oneof_int32_value(), 5); } +TEST_P(JsonTest, RepeatedSingularKeys) { + auto m = ToProto(R"json({ + "int32Value": 1, + "int32Value": 2 + })json"); + EXPECT_OK(m); + EXPECT_EQ(m->int32_value(), 2); +} + +TEST_P(JsonTest, RepeatedRepeatedKeys) { + auto m = ToProto(R"json({ + "repeatedInt32Value": [1], + "repeatedInt32Value": [2, 3] + })json"); + EXPECT_OK(m); + EXPECT_THAT(m->repeated_int32_value(), ElementsAre(1, 2, 3)); +} + +TEST_P(JsonTest, RepeatedOneofKeys) { + EXPECT_THAT(ToProto(R"json({ + "oneofInt32Value": 1, + "oneofStringValue": "foo" + })json"), + StatusIs(util::StatusCode::kInvalidArgument)); +} + TEST_P(JsonTest, TestParseIgnoreUnknownFields) { JsonParseOptions options; options.ignore_unknown_fields = true; EXPECT_OK(ToProto(R"({"unknownName":0})", options)); + + TestMessage m; + m.GetReflection()->MutableUnknownFields(&m)->AddFixed32(9001, 9001); + m.GetReflection()->MutableUnknownFields(&m)->AddFixed64(9001, 9001); + m.GetReflection()->MutableUnknownFields(&m)->AddVarint(9001, 9001); + m.GetReflection()->MutableUnknownFields(&m)->AddLengthDelimited(9001, "9001"); + EXPECT_THAT(ToJson(m), IsOkAndHolds("{}")); } TEST_P(JsonTest, TestParseErrors) { @@ -578,9 +587,8 @@ TEST_P(JsonTest, TestDynamicMessage) { EXPECT_TRUE(generated.ParseFromString(message->SerializeAsString())); EXPECT_EQ(generated.int32_value(), 1024); - ASSERT_EQ(generated.repeated_int32_value_size(), 2); - EXPECT_EQ(generated.repeated_int32_value(0), 1); - EXPECT_EQ(generated.repeated_int32_value(1), 2); + EXPECT_THAT(generated.repeated_int32_value(), ElementsAre(1, 2)); + EXPECT_EQ(generated.message_value().value(), 2048); ASSERT_EQ(generated.repeated_message_value_size(), 2); EXPECT_EQ(generated.repeated_message_value(0).value(), 40); @@ -825,11 +833,11 @@ TEST_P(JsonTest, TestParsingEnumCaseSensitive) { EXPECT_EQ(m.enum_value(), proto3::FOO); } - TEST_P(JsonTest, TestParsingEnumLowercase) { JsonParseOptions options; options.case_insensitive_enum_parsing = true; - auto m = ToProto(R"json({"enum_value": "TLSv1_2"})json", options); + auto m = + ToProto(R"json({"enum_value": "TLSv1_2"})json", options); ASSERT_OK(m); EXPECT_THAT(m->enum_value(), proto3::TLSv1_2); } @@ -851,10 +859,7 @@ TEST_P(JsonTest, TestOverwriteRepeated) { m.add_repeated_int32_value(5); ASSERT_OK(ToProto(m, R"json({"repeated_int32_value": [1, 2, 3]})json")); - EXPECT_EQ(m.repeated_int32_value_size(), 3); - EXPECT_EQ(m.repeated_int32_value(0), 1); - EXPECT_EQ(m.repeated_int32_value(1), 2); - EXPECT_EQ(m.repeated_int32_value(2), 3); + EXPECT_THAT(m.repeated_int32_value(), ElementsAre(1, 2, 3)); } @@ -869,7 +874,8 @@ TEST_P(JsonTest, TestDuration) { EXPECT_EQ(m->value().seconds(), 123456); EXPECT_EQ(m->value().nanos(), 789000000); - EXPECT_EQ(m->repeated_value().size(), 2); + + EXPECT_THAT(m->repeated_value(), SizeIs(2)); EXPECT_EQ(m->repeated_value(0).seconds(), 0); EXPECT_EQ(m->repeated_value(0).nanos(), 100000000); EXPECT_EQ(m->repeated_value(1).seconds(), 999); @@ -904,7 +910,7 @@ TEST_P(JsonTest, TestTimestamp) { EXPECT_EQ(m->value().seconds(), 825422400); EXPECT_EQ(m->value().nanos(), 0); - EXPECT_EQ(m->repeated_value().size(), 1); + EXPECT_THAT(m->repeated_value(), SizeIs(1)); EXPECT_EQ(m->repeated_value(0).seconds(), 253402300799); EXPECT_EQ(m->repeated_value(0).nanos(), 0); @@ -954,10 +960,7 @@ TEST_P(JsonTest, TestFieldMask) { )json"); ASSERT_OK(m); - EXPECT_EQ(m->value().paths_size(), 2); - EXPECT_EQ(m->value().paths(0), "foo"); - EXPECT_EQ(m->value().paths(1), "bar.baz_baz"); - + EXPECT_THAT(m->value().paths(), ElementsAre("foo", "bar.baz_baz")); EXPECT_THAT(ToJson(*m), IsOkAndHolds(R"({"value":"foo,bar.bazBaz"})")); auto m2 = ToProto(R"json( @@ -969,8 +972,7 @@ TEST_P(JsonTest, TestFieldMask) { )json"); ASSERT_OK(m2); - EXPECT_EQ(m2->value().paths_size(), 1); - EXPECT_EQ(m2->value().paths(0), "yep.really"); + EXPECT_THAT(m2->value().paths(), ElementsAre("yep.really")); } TEST_P(JsonTest, TestLegalNullsInArray) { @@ -979,15 +981,15 @@ TEST_P(JsonTest, TestLegalNullsInArray) { })json"); ASSERT_OK(m); - EXPECT_EQ(m->repeated_null_value_size(), 1); - EXPECT_EQ(m->repeated_null_value(0), google::protobuf::NULL_VALUE); + EXPECT_THAT(m->repeated_null_value(), + ElementsAre(google::protobuf::NULL_VALUE)); auto m2 = ToProto(R"json({ "repeatedValue": [null] })json"); ASSERT_OK(m2); - EXPECT_EQ(m2->repeated_value_size(), 1); + ASSERT_THAT(m2->repeated_value(), SizeIs(1)); EXPECT_TRUE(m2->repeated_value(0).has_null_value()); }