// Protocol Buffers - Google's data interchange format // Copyright 2025 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 #include "upb/mini_table/generated_registry.h" #include #include // NOLINT #include #include "google/protobuf/descriptor.upb_minitable.h" #include #include "absl/synchronization/barrier.h" #include "upb/mini_table/extension.h" #include "upb/mini_table/extension_registry.h" #include "upb/test/custom_options.upb_minitable.h" #include "upb/test/editions_test.upb_minitable.h" #include "upb/test/test_multiple_files.upb_minitable.h" #include "upb/test/test_multiple_files2.upb_minitable.h" namespace { class GeneratedRegistryTest : public ::testing::Test { protected: void SetUp() override { ref_ = upb_GeneratedRegistry_Load(); ASSERT_NE(ref_, nullptr); reg_ = upb_GeneratedRegistry_Get(ref_); ASSERT_NE(reg_, nullptr); } void TearDown() override { upb_GeneratedRegistry_Release(ref_); } const upb_GeneratedRegistryRef* ref_ = nullptr; const upb_ExtensionRegistry* reg_ = nullptr; }; TEST_F(GeneratedRegistryTest, LocalExtensionExists) { // Test an arbitrary extension defined in the same .proto file that we expect // to be linked in. // Force linkage and sanity check extension numbers. ASSERT_EQ(upb_MiniTableExtension_Number(&upb_test_2023_ext_ext), 100); EXPECT_NE(upb_ExtensionRegistry_Lookup( reg_, &upb__test_02023__EditionsMessage_msg_init, 100), nullptr); } TEST_F(GeneratedRegistryTest, ForeignExtensionExists) { // Test an arbitrary extension defined by an import that we expect to be // linked in. // Force linkage and sanity check extension numbers. ASSERT_EQ(upb_MiniTableExtension_Number(&upb_message_opt_ext), 7739036); EXPECT_NE(upb_ExtensionRegistry_Lookup(reg_, &google__protobuf__MessageOptions_msg_init, 7739036), nullptr); } TEST_F(GeneratedRegistryTest, UnlinkedExtensionDoesNotExist) { // Test an arbitrary extension defined by an option import that we do not // expect to be linked in. // Corresponds to upb.message_opt_unlinked in custom_options_unlinked.proto. EXPECT_EQ(upb_ExtensionRegistry_Lookup(reg_, &google__protobuf__MessageOptions_msg_init, 7739037), nullptr); } TEST_F(GeneratedRegistryTest, MultipleFilesExtensionExists) { // Test that multiple .proto files from the same proto_library can be linked // in and registered. // Force linkage and sanity check extension numbers. ASSERT_EQ(upb_MiniTableExtension_Number(&upb_multiple_files_ext1_ext), 100); ASSERT_EQ(upb_MiniTableExtension_Number(&upb_multiple_files_ext2_ext), 100); EXPECT_NE(upb_ExtensionRegistry_Lookup( reg_, &upb__MultipleFilesMessage1_msg_init, 100), nullptr); EXPECT_NE(upb_ExtensionRegistry_Lookup( reg_, &upb__MultipleFilesMessage2_msg_init, 100), nullptr); } TEST_F(GeneratedRegistryTest, ReleaseOnError) { upb_GeneratedRegistry_Release(nullptr); } TEST_F(GeneratedRegistryTest, GetOnError) { EXPECT_EQ(upb_GeneratedRegistry_Get(nullptr), nullptr); } // On 32-bit systems, the stack size is smaller, so we can't run too many // concurrent threads without overflowing the stack. constexpr int kRaceIterations = sizeof(void*) < 8 ? 100 : 2000; TEST(GeneratedRegistryRaceTest, Load) { absl::Barrier barrier(kRaceIterations); std::vector threads; std::array refs; for (int i = 0; i < kRaceIterations; ++i) { threads.push_back(std::thread([&, i]() { barrier.Block(); const upb_GeneratedRegistryRef* ref = upb_GeneratedRegistry_Load(); EXPECT_NE(upb_GeneratedRegistry_Get(ref), nullptr); refs[i] = ref; })); } for (auto& t : threads) t.join(); for (int i = 0; i < kRaceIterations; ++i) { EXPECT_NE(refs[i], nullptr); upb_GeneratedRegistry_Release(refs[i]); } } TEST(GeneratedRegistryRaceTest, Release) { absl::Barrier barrier(kRaceIterations); std::vector threads; std::array refs; for (int i = 0; i < kRaceIterations; ++i) { refs[i] = upb_GeneratedRegistry_Load(); ASSERT_NE(refs[i], nullptr); } for (int i = 0; i < kRaceIterations; ++i) { threads.push_back(std::thread([&, i]() { EXPECT_NE(upb_GeneratedRegistry_Get(refs[i]), nullptr); barrier.Block(); upb_GeneratedRegistry_Release(refs[i]); })); } for (auto& t : threads) t.join(); } TEST(GeneratedRegistryRaceTest, LoadRelease) { absl::Barrier barrier(kRaceIterations); std::vector threads; for (int i = 0; i < kRaceIterations; ++i) { threads.push_back(std::thread([&]() { barrier.Block(); const upb_GeneratedRegistryRef* ref = upb_GeneratedRegistry_Load(); EXPECT_NE(upb_GeneratedRegistry_Get(ref), nullptr); upb_GeneratedRegistry_Release(ref); })); } for (auto& t : threads) t.join(); } TEST(GeneratedRegistryRaceTest, ReleaseLastAndLoadMultiple) { for (int i = 0; i < kRaceIterations; ++i) { // Load one reference. The registry is now alive with ref_count == 1. const upb_GeneratedRegistryRef* ref = upb_GeneratedRegistry_Load(); ASSERT_NE(ref, nullptr); constexpr int kNumLoaders = 4; absl::Barrier barrier(kNumLoaders + 1); std::vector threads; threads.push_back(std::thread([&]() { barrier.Block(); // This release should bring the ref_count to 0 and trigger cleanup. upb_GeneratedRegistry_Release(ref); })); for (int j = 0; j < kNumLoaders; ++j) { threads.push_back(std::thread([&]() { barrier.Block(); const upb_GeneratedRegistryRef* ref2 = upb_GeneratedRegistry_Load(); EXPECT_NE(upb_GeneratedRegistry_Get(ref2), nullptr); upb_GeneratedRegistry_Release(ref2); })); } for (auto& t : threads) t.join(); } } } // namespace