diff --git a/upb/BUILD b/upb/BUILD index 19030e6445..0eac0e5391 100644 --- a/upb/BUILD +++ b/upb/BUILD @@ -425,14 +425,11 @@ alias( visibility = ["//upb:friends"], ) -# begin:google_only -# alias( -# name = "mini_table_compat", -# actual = "//upb/upb/mini_table:compat", -# compatible_with = ["//buildenv/target:non_prod"], -# visibility = ["//upb:friends"], -# ) -# end:google_only +alias( + name = "mini_table_compat", + actual = "//upb/upb/mini_table:compat", + visibility = ["//upb:friends"], +) alias( name = "mini_table_internal", @@ -559,6 +556,7 @@ upb_amalgamation( ":mini_descriptor", ":mini_descriptor_internal", ":mini_table", + ":mini_table_compat", ":mini_table_internal", ":port", ":reflection", diff --git a/upb/upb/mini_table/BUILD b/upb/upb/mini_table/BUILD index 315b9d9587..44d47672e9 100644 --- a/upb/upb/mini_table/BUILD +++ b/upb/upb/mini_table/BUILD @@ -10,25 +10,24 @@ load( "UPB_DEFAULT_COPTS", ) -# begin:google_only -# cc_library( -# name = "compat", -# srcs = [ -# "compat.c", -# ], -# hdrs = [ -# "compat.h", -# ], -# compatible_with = ["//buildenv/target:non_prod"], -# copts = UPB_DEFAULT_COPTS, -# visibility = ["//upb:__pkg__"], -# deps = [ -# ":mini_table", -# "//upb:base", -# "//upb:port", -# ], -# ) -# end:google_only +cc_library( + name = "compat", + srcs = [ + "compat.c", + ], + hdrs = [ + "compat.h", + ], + copts = UPB_DEFAULT_COPTS, + visibility = ["//visibility:public"], + deps = [ + ":mini_table", + "//upb:base", + "//upb:hash", + "//upb:mem", + "//upb:port", + ], +) cc_library( name = "mini_table", @@ -78,6 +77,18 @@ cc_library( ], ) +cc_test( + name = "compat_test", + srcs = ["compat_test.cc"], + deps = [ + "//upb:mini_table_compat", + "//upb/upb/test:test_messages_proto2_upb_proto", + "//upb/upb/test:test_messages_proto3_upb_proto", + "//upb/upb/test:test_upb_proto", + "@com_google_googletest//:gtest_main", + ], +) + # begin:github_only filegroup( name = "source_files", diff --git a/upb/upb/mini_table/compat.c b/upb/upb/mini_table/compat.c index 1ee8bb457b..e8fa81d7de 100644 --- a/upb/upb/mini_table/compat.c +++ b/upb/upb/mini_table/compat.c @@ -8,51 +8,102 @@ #include "upb/upb/mini_table/compat.h" #include "upb/upb/base/descriptor_constants.h" +#include "upb/upb/hash/common.h" +#include "upb/upb/hash/int_table.h" +#include "upb/upb/mem/arena.h" #include "upb/upb/mini_table/field.h" #include "upb/upb/mini_table/message.h" // Must be last. #include "upb/upb/port/def.inc" -static bool upb_deep_check(const upb_MiniTable* src, const upb_MiniTable* dst, - bool eq) { - if (src->field_count != dst->field_count) return false; - +// Checks if source and target mini table fields are identical. +// +// If the field is a sub message and sub messages are identical we record +// the association in table. +// +// Hashing the source sub message mini table and it's equivalent in the table +// stops recursing when a cycle is detected and instead just checks if the +// destination table is equal. +static upb_MiniTableEquals_Status upb_deep_check(const upb_MiniTable* src, + const upb_MiniTable* dst, + upb_inttable* table, + upb_Arena** arena) { + if (src->field_count != dst->field_count) + return kUpb_MiniTableEquals_NotEqual; + bool marked_src = false; for (int i = 0; i < src->field_count; i++) { const upb_MiniTableField* src_field = &src->fields[i]; const upb_MiniTableField* dst_field = upb_MiniTable_FindFieldByNumber(dst, src_field->number); if (upb_MiniTableField_CType(src_field) != - upb_MiniTableField_CType(dst_field)) return false; + upb_MiniTableField_CType(dst_field)) + return false; if (src_field->mode != dst_field->mode) return false; if (src_field->offset != dst_field->offset) return false; if (src_field->presence != dst_field->presence) return false; if (src_field->UPB_PRIVATE(submsg_index) != - dst_field->UPB_PRIVATE(submsg_index)) return false; + dst_field->UPB_PRIVATE(submsg_index)) + return kUpb_MiniTableEquals_NotEqual; // Go no further if we are only checking for compatibility. - if (!eq) continue; + if (!table) continue; if (upb_MiniTableField_CType(src_field) == kUpb_CType_Message) { + if (!*arena) { + *arena = upb_Arena_New(); + if (!upb_inttable_init(table, *arena)) { + return kUpb_MiniTableEquals_OutOfMemory; + } + } + if (!marked_src) { + marked_src = true; + upb_value val; + val.val = (uint64_t)dst; + if (!upb_inttable_insert(table, (uintptr_t)src, val, *arena)) { + return kUpb_MiniTableEquals_OutOfMemory; + } + } const upb_MiniTable* sub_src = upb_MiniTable_GetSubMessageTable(src, src_field); const upb_MiniTable* sub_dst = upb_MiniTable_GetSubMessageTable(dst, dst_field); - if (sub_src != NULL && !upb_MiniTable_Equals(sub_src, sub_dst)) { - return false; + if (sub_src != NULL) { + upb_value cmp; + if (upb_inttable_lookup(table, (uintptr_t)sub_src, &cmp)) { + // We already compared this src before. Check if same dst. + if (cmp.val != (uint64_t)sub_dst) { + return kUpb_MiniTableEquals_NotEqual; + } + } else { + // Recurse if not already visited. + upb_MiniTableEquals_Status s = + upb_deep_check(sub_src, sub_dst, table, arena); + if (s != kUpb_MiniTableEquals_Equal) { + return s; + } + } } } } - - return true; + return kUpb_MiniTableEquals_Equal; } bool upb_MiniTable_Compatible(const upb_MiniTable* src, const upb_MiniTable* dst) { - return upb_deep_check(src, dst, false); + return upb_deep_check(src, dst, NULL, NULL); } -bool upb_MiniTable_Equals(const upb_MiniTable* src, const upb_MiniTable* dst) { - return upb_deep_check(src, dst, true); +upb_MiniTableEquals_Status upb_MiniTable_Equals(const upb_MiniTable* src, + const upb_MiniTable* dst) { + // Arena allocated on demand for hash table. + upb_Arena* arena = NULL; + // Table to keep track of visited mini tables to guard against cycles. + upb_inttable table; + upb_MiniTableEquals_Status status = upb_deep_check(src, dst, &table, &arena); + if (arena) { + upb_Arena_Free(arena); + } + return status; } diff --git a/upb/upb/mini_table/compat.h b/upb/upb/mini_table/compat.h index 843262cbfb..091d878296 100644 --- a/upb/upb/mini_table/compat.h +++ b/upb/upb/mini_table/compat.h @@ -26,8 +26,15 @@ extern "C" { bool upb_MiniTable_Compatible(const upb_MiniTable* src, const upb_MiniTable* dst); +typedef enum { + kUpb_MiniTableEquals_NotEqual, + kUpb_MiniTableEquals_Equal, + kUpb_MiniTableEquals_OutOfMemory, +} upb_MiniTableEquals_Status; + // Checks equality of mini tables originating from different language runtimes. -bool upb_MiniTable_Equals(const upb_MiniTable* src, const upb_MiniTable* dst); +upb_MiniTableEquals_Status upb_MiniTable_Equals(const upb_MiniTable* src, + const upb_MiniTable* dst); #ifdef __cplusplus } /* extern "C" */ diff --git a/upb/upb/mini_table/compat_test.cc b/upb/upb/mini_table/compat_test.cc new file mode 100644 index 0000000000..59ae5cd1d8 --- /dev/null +++ b/upb/upb/mini_table/compat_test.cc @@ -0,0 +1,34 @@ +// 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 + +/* Test of mini table accessors. + * + * Messages are created and mutated using generated code, and then + * accessed through reflective APIs exposed through mini table accessors. + */ + +#include "upb/upb/mini_table/compat.h" + +#include +#include "google/protobuf/test_messages_proto2.upb.h" +#include "google/protobuf/test_messages_proto3.upb.h" + +namespace { + +TEST(GeneratedCode, EqualsTestProto2) { + EXPECT_TRUE(upb_MiniTable_Equals( + &protobuf_test_messages_proto2_ProtoWithKeywords_msg_init, + &protobuf_test_messages_proto2_ProtoWithKeywords_msg_init)); +} + +TEST(GeneratedCode, EqualsTestProto3) { + EXPECT_TRUE(upb_MiniTable_Equals( + &protobuf_test_messages_proto3_TestAllTypesProto3_msg_init, + &protobuf_test_messages_proto3_TestAllTypesProto3_msg_init)); +} + +} // namespace