// 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/debug_string.h" #include #include #include #include #include #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/enum.h" #include "upb/mini_table/field.h" #include "upb/mini_table/internal/field.h" #include "upb/mini_table/internal/message.h" #include "upb/mini_table/message.h" #include "upb/port/vsnprintf_compat.h" // Must be last. #include "upb/port/def.inc" typedef struct { char* buf; char* ptr; char* end; int overflow; upb_Arena* arena; int count; // This table maps from a pointer to a 64-bit integer. The lower 32 bits are // a unique ID for the object. The upper 32 bits are a flag that is 0x1 // if the object has already been printed. upb_inttable inttable; } upb_MiniTablePrinter; UPB_PRINTF(2, 3) static void upb_MiniTablePrinter_Printf(upb_MiniTablePrinter* p, const char* fmt, ...) { size_t n; size_t have = p->end - p->ptr; va_list args; va_start(args, fmt); n = _upb_vsnprintf(p->ptr, have, fmt, args); va_end(args); if (UPB_LIKELY(have > n)) { p->ptr += n; } else { p->ptr = UPB_PTRADD(p->ptr, have); p->overflow += (n - have); } } static size_t upb_MiniTablePrinter_NullTerminate(upb_MiniTablePrinter* p, size_t size) { size_t ret = p->ptr - p->buf + p->overflow; if (size > 0) { if (p->ptr == p->end) p->ptr--; *p->ptr = '\0'; } return ret; } static int upb_MiniTablePrinter_InsertNext(upb_MiniTablePrinter* p, const void* key, bool visited) { uint64_t id = p->count++; upb_inttable_insert(&p->inttable, (intptr_t)key, upb_value_uint64(id | (visited ? 0x100000000 : 0)), p->arena); return id; } // Returns the ID of the object referenced by key, but does *not* mark the // object as visited. This is used for printing a reference to another object // that may or may not have been printed yet. static int upb_MiniTablePrinter_GetIdForRef(upb_MiniTablePrinter* p, const void* key) { upb_value v; if (upb_inttable_lookup(&p->inttable, (intptr_t)key, &v)) { return (int)upb_value_getuint64(v); } return upb_MiniTablePrinter_InsertNext(p, key, false); } // Returns the ID of the object referenced by key, and marks the object as // visited. This is used for printing the object itself. static int upb_MiniTablePrinter_GetIdForEmit(upb_MiniTablePrinter* p, const void* key) { UPB_ASSERT(key); upb_value v; if (upb_inttable_lookup(&p->inttable, (intptr_t)key, &v)) { uint64_t val = upb_value_getuint64(v); if (val >> 32) return -1; upb_inttable_replace(&p->inttable, (intptr_t)key, upb_value_int64(val | 0x100000000)); return (int)val; } return upb_MiniTablePrinter_InsertNext(p, key, true); } static void upb_MiniTablePrinter_PrintEnum(upb_MiniTablePrinter* p, const upb_MiniTableEnum* enum_) { int id = upb_MiniTablePrinter_GetIdForEmit(p, enum_); if (id < 0) return; upb_MiniTablePrinter_Printf(p, "MiniTableEnum#%d {\n", id); upb_MiniTablePrinter_Printf(p, " .mask_limit = %d\n", enum_->UPB_PRIVATE(mask_limit)); upb_MiniTablePrinter_Printf(p, " .value_count = %d\n", enum_->UPB_PRIVATE(value_count)); upb_MiniTablePrinter_Printf(p, " .values = {\n"); for (uint32_t i = 0; i < enum_->UPB_PRIVATE(mask_limit); i++) { if (!upb_MiniTableEnum_CheckValue(enum_, i)) continue; upb_MiniTablePrinter_Printf(p, " %d,\n", (int)i); } const uint32_t* start = &enum_->UPB_PRIVATE(data)[enum_->UPB_PRIVATE(mask_limit) / 32]; for (uint32_t i = 0; i < enum_->UPB_PRIVATE(value_count); i++) { upb_MiniTablePrinter_Printf(p, " %d,\n", (int)start[i]); } upb_MiniTablePrinter_Printf(p, " }\n"); upb_MiniTablePrinter_Printf(p, "}\n\n"); } static void upb_MiniTablePrinter_PrintField(upb_MiniTablePrinter* p, const upb_MiniTable* mini_table, const upb_MiniTableField* field) { upb_MiniTablePrinter_Printf(p, " MiniTableField {\n"); upb_MiniTablePrinter_Printf(p, " .number = %d\n", field->UPB_PRIVATE(number)); upb_MiniTablePrinter_Printf(p, " .offset = %d\n", field->UPB_PRIVATE(offset)); upb_MiniTablePrinter_Printf(p, " .presence = %d", field->presence); if (field->presence > 0) { upb_MiniTablePrinter_Printf(p, " (hasbit=%d)\n", field->presence); } else if (field->presence < 0) { upb_MiniTablePrinter_Printf(p, " (oneof_index=%d)\n", ~field->presence); } else { upb_MiniTablePrinter_Printf(p, " (no explicit presence)\n"); } if (field->UPB_PRIVATE(submsg_ofs) != kUpb_NoSub) { upb_MiniTablePrinter_Printf(p, " .submsg_ofs = %d\n", field->UPB_PRIVATE(submsg_ofs)); } upb_MiniTablePrinter_Printf(p, " .type = %d\n", field->UPB_PRIVATE(descriptortype)); upb_MiniTablePrinter_Printf(p, " .mode = %02x (", field->UPB_PRIVATE(mode)); switch (field->UPB_PRIVATE(mode) & kUpb_FieldMode_Mask) { case kUpb_FieldMode_Scalar: upb_MiniTablePrinter_Printf(p, "Scalar"); break; case kUpb_FieldMode_Array: upb_MiniTablePrinter_Printf(p, "Array"); break; case kUpb_FieldMode_Map: upb_MiniTablePrinter_Printf(p, "Map"); break; } switch (field->UPB_PRIVATE(mode) >> kUpb_FieldRep_Shift) { case kUpb_FieldRep_1Byte: upb_MiniTablePrinter_Printf(p, " | 1Byte"); break; case kUpb_FieldRep_4Byte: upb_MiniTablePrinter_Printf(p, " | 4Byte"); break; case kUpb_FieldRep_8Byte: upb_MiniTablePrinter_Printf(p, " | 8Byte"); break; case kUpb_FieldRep_StringView: upb_MiniTablePrinter_Printf(p, " | StringView"); break; } if (field->UPB_PRIVATE(mode) & kUpb_LabelFlags_IsPacked) { upb_MiniTablePrinter_Printf(p, " | Packed"); } if (field->UPB_PRIVATE(mode) & kUpb_LabelFlags_IsExtension) { upb_MiniTablePrinter_Printf(p, " | Extension"); } if (field->UPB_PRIVATE(mode) & kUpb_LabelFlags_IsAlternate) { upb_MiniTablePrinter_Printf(p, " | Alternate"); } upb_MiniTablePrinter_Printf(p, ")\n"); if (field->UPB_PRIVATE(submsg_ofs) != kUpb_NoSub) { if (upb_MiniTableField_CType(field) == kUpb_CType_Message) { int id = upb_MiniTablePrinter_GetIdForRef(p, upb_MiniTable_SubMessage(field)); upb_MiniTablePrinter_Printf(p, " .submsg = MiniTable#%d\n", id); } else { int id = upb_MiniTablePrinter_GetIdForRef( p, upb_MiniTable_GetSubEnumTable(field)); upb_MiniTablePrinter_Printf(p, " .subenum = MiniTableEnum#%d\n", id); } } upb_MiniTablePrinter_Printf(p, " },\n"); } static void upb_MiniTablePrinter_PrintMessage(upb_MiniTablePrinter* p, const upb_MiniTable* mini_table) { int id = upb_MiniTablePrinter_GetIdForEmit(p, mini_table); if (id < 0) return; upb_MiniTablePrinter_Printf(p, "MiniTable#%d {\n", id); upb_MiniTablePrinter_Printf(p, " .size = %d\n", mini_table->UPB_PRIVATE(size)); upb_MiniTablePrinter_Printf(p, " .required_count = %d\n", mini_table->UPB_PRIVATE(required_count)); upb_MiniTablePrinter_Printf(p, " .table_mask = %02x\n", mini_table->UPB_PRIVATE(table_mask)); upb_MiniTablePrinter_Printf(p, " .dense_below = %d\n", mini_table->UPB_PRIVATE(dense_below)); upb_MiniTablePrinter_Printf(p, " .ext = %02x (", mini_table->UPB_PRIVATE(ext)); switch (mini_table->UPB_PRIVATE(ext) & 3) { case kUpb_ExtMode_NonExtendable: upb_MiniTablePrinter_Printf(p, "NonExtendable"); break; case kUpb_ExtMode_Extendable: upb_MiniTablePrinter_Printf(p, "Extendable"); break; case kUpb_ExtMode_IsMessageSet: upb_MiniTablePrinter_Printf(p, "MessageSet"); break; case kUpb_ExtMode_IsMessageSet_ITEM: upb_MiniTablePrinter_Printf(p, "MessageSetItem"); break; } if (mini_table->UPB_PRIVATE(ext) & kUpb_ExtMode_IsMapEntry) { upb_MiniTablePrinter_Printf(p, " | MapEntry"); } upb_MiniTablePrinter_Printf(p, ")\n"); upb_MiniTablePrinter_Printf(p, " .fields[%d] = {\n", mini_table->UPB_PRIVATE(field_count)); for (int i = 0; i < mini_table->UPB_PRIVATE(field_count); i++) { const upb_MiniTableField* field = &mini_table->UPB_PRIVATE(fields)[i]; upb_MiniTablePrinter_PrintField(p, mini_table, field); } upb_MiniTablePrinter_Printf(p, " }\n"); int mask = (int8_t)mini_table->UPB_PRIVATE(table_mask); if (mask != -1) { int size = (mask >> 3) + 1; upb_MiniTablePrinter_Printf(p, " .fasttable[%d] = {\n", size); for (int i = 0; i < size; i++) { const _upb_FastTable_Entry* entry = &mini_table->UPB_PRIVATE(fasttable)[i]; upb_MiniTablePrinter_Printf(p, " FastTableEntry {\n"); upb_MiniTablePrinter_Printf(p, " .field_data = %016" PRIx64 ",\n", entry->field_data); upb_MiniTablePrinter_Printf(p, " .field_parser = %p\n", entry->field_parser); upb_MiniTablePrinter_Printf(p, " .field_number = %d\n", (((int)entry->field_data >> 3) & 0xf) | (((int)entry->field_data >> 4) & 0x7f0)); upb_MiniTablePrinter_Printf(p, " }\n"); } upb_MiniTablePrinter_Printf(p, " }\n"); } upb_MiniTablePrinter_Printf(p, "}\n\n"); for (int i = 0; i < mini_table->UPB_PRIVATE(field_count); i++) { const upb_MiniTableField* field = &mini_table->UPB_PRIVATE(fields)[i]; if (field->UPB_PRIVATE(submsg_ofs) == kUpb_NoSub) continue; if (upb_MiniTableField_CType(field) == kUpb_CType_Message) { upb_MiniTablePrinter_PrintMessage(p, upb_MiniTable_SubMessage(field)); } else { upb_MiniTablePrinter_PrintEnum(p, upb_MiniTable_GetSubEnumTable(field)); } } } size_t upb_MiniTable_DebugString(const upb_MiniTable* mini_table, char* buf, size_t size) { upb_MiniTablePrinter p = {buf, buf, buf + size, 0, upb_Arena_New(), 0}; if (!p.arena) return 0; if (!upb_inttable_init(&p.inttable, p.arena)) return 0; upb_MiniTablePrinter_PrintMessage(&p, mini_table); upb_Arena_Free(p.arena); return upb_MiniTablePrinter_NullTerminate(&p, size); }