| // 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 <inttypes.h> | |
| #include <stdarg.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <stdio.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/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++; | |
| bool ok = upb_inttable_insert( | |
| &p->inttable, (intptr_t)key, | |
| upb_value_uint64(id | (visited ? 0x100000000 : 0)), p->arena); | |
| UPB_ASSERT(ok); | |
| 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"); | |
| } | |
| if (mini_table->UPB_PRIVATE(ext) & kUpb_ExtMode_AllFastFieldsAssigned) { | |
| upb_MiniTablePrinter_Printf(p, " | AllFieldsAssigned"); | |
| } | |
| 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"); | |
| #if UPB_FASTTABLE | |
| 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 = %s\n", | |
| entry->field_parser ? "[ptr]" : "(nil)"); | |
| 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"); | |
| } | |
| #endif | |
| 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); | |
| } |