// 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 #include "upb/mini_table/compat.h" #include "upb/base/descriptor_constants.h" #include "upb/hash/common.h" #include "upb/hash/int_table.h" #include "upb/mem/arena.h" #include "upb/mini_table/field.h" #include "upb/mini_table/message.h" // Must be last. #include "upb/port/def.inc" // 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; 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 kUpb_MiniTableEquals_NotEqual; // Go no further if we are only checking for compatibility. 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_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 kUpb_MiniTableEquals_Equal; } bool upb_MiniTable_Compatible(const upb_MiniTable* src, const upb_MiniTable* dst) { return upb_deep_check(src, dst, NULL, NULL); } 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; }