UPB text encoder without using reflection for Rust (used for a message's Debug trait) that will print out field number to value entries instead of field name to value entries of a message like how it's expected for the usual text format using reflection.
General test for it is done in Rust, and then extensions are tested in UPB as they're not currently supported in Rust-upb. PiperOrigin-RevId: 651113583pull/17217/head
parent
32bcf0bead
commit
f9dd9ce66e
22 changed files with 1256 additions and 466 deletions
@ -0,0 +1,88 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2024 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
|
||||
|
||||
use googletest::prelude::*; |
||||
use map_unittest_rust_proto::TestMapWithMessages; |
||||
use protobuf_upb::proto; |
||||
use unittest_rust_proto::{ |
||||
test_all_types::NestedEnum as NestedEnumProto2, |
||||
test_all_types::NestedMessage as NestedMessageProto2, TestAllTypes as TestAllTypesProto2, |
||||
}; |
||||
|
||||
#[test] |
||||
fn test_debug_string() { |
||||
let mut msg = proto!(TestAllTypesProto2 { |
||||
optional_int32: 42, |
||||
optional_string: "Hello World", |
||||
optional_nested_enum: NestedEnumProto2::Bar, |
||||
oneof_uint32: 452235, |
||||
optional_nested_message: proto!(NestedMessageProto2 { bb: 100 }), |
||||
}); |
||||
let mut repeated_string = msg.repeated_string_mut(); |
||||
repeated_string.push("Hello World"); |
||||
repeated_string.push("Hello World"); |
||||
repeated_string.push("Hello World"); |
||||
|
||||
let mut msg_map = TestMapWithMessages::new(); |
||||
println!("EMPTY MSG: {:?}", msg_map); // Make sure that we can print an empty message.
|
||||
msg_map.map_string_all_types_mut().insert("hello", msg.as_view()); |
||||
msg_map.map_string_all_types_mut().insert("fizz", msg.as_view()); |
||||
msg_map.map_string_all_types_mut().insert("boo", msg.as_view()); |
||||
|
||||
println!("{:?}", msg_map); |
||||
println!("{:?}", msg_map.as_view()); // Make sure that we can print as_view
|
||||
println!("{:?}", msg_map.as_mut()); // Make sure that we can print as_mut
|
||||
let golden = r#"12 { |
||||
key: "hello" |
||||
value { |
||||
1: 42 |
||||
14: "Hello World" |
||||
18 { |
||||
1: 100 |
||||
} |
||||
21: 2 |
||||
44: "Hello World" |
||||
44: "Hello World" |
||||
44: "Hello World" |
||||
111: 452235 |
||||
} |
||||
} |
||||
12 { |
||||
key: "fizz" |
||||
value { |
||||
1: 42 |
||||
14: "Hello World" |
||||
18 { |
||||
1: 100 |
||||
} |
||||
21: 2 |
||||
44: "Hello World" |
||||
44: "Hello World" |
||||
44: "Hello World" |
||||
111: 452235 |
||||
} |
||||
} |
||||
12 { |
||||
key: "boo" |
||||
value { |
||||
1: 42 |
||||
14: "Hello World" |
||||
18 { |
||||
1: 100 |
||||
} |
||||
21: 2 |
||||
44: "Hello World" |
||||
44: "Hello World" |
||||
44: "Hello World" |
||||
111: 452235 |
||||
} |
||||
} |
||||
"#; |
||||
// C strings are null terminated while Rust strings are not.
|
||||
let null_terminated_str = format!("{}\0", golden); |
||||
assert_that!(format!("{:?}", msg_map), eq(null_terminated_str.as_str())); |
||||
} |
@ -0,0 +1,66 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2024 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
|
||||
|
||||
use crate::{upb_MiniTable, RawMessage}; |
||||
|
||||
extern "C" { |
||||
/// Returns the minimum needed length (excluding NULL) that `buf` has to be
|
||||
/// to hold the `msg`s debug string.
|
||||
///
|
||||
/// SAFETY:
|
||||
/// - `msg` is pointing at a valid upb_Message with associated minitable
|
||||
/// `mt`
|
||||
/// - `buf` is legally writable for `size` bytes (`buf` may be nullptr if
|
||||
/// `size` is 0)
|
||||
fn upb_DebugString( |
||||
msg: RawMessage, |
||||
mt: *const upb_MiniTable, |
||||
options: i32, |
||||
buf: *mut u8, |
||||
size: usize, |
||||
) -> usize; |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
#[repr(i32)] |
||||
enum Options { |
||||
// When set, prints everything on a single line.
|
||||
SingleLine = 1, |
||||
|
||||
// When set, unknown fields are not printed.
|
||||
SkipUnknown = 2, |
||||
|
||||
// When set, maps are *not* sorted (this avoids allocating tmp mem).
|
||||
NoSortMaps = 4, |
||||
} |
||||
|
||||
/// Returns a string of field number to value entries of a message.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `mt` must correspond to the `msg`s minitable.
|
||||
pub unsafe fn debug_string(msg: RawMessage, mt: *const upb_MiniTable) -> String { |
||||
// Only find out the length first to then allocate a buffer of the minimum size
|
||||
// needed.
|
||||
// SAFETY:
|
||||
// - `msg` is a legally dereferencable upb_Message whose associated minitable is
|
||||
// `mt`
|
||||
// - `buf` is nullptr and `buf_len` is 0
|
||||
let len = |
||||
unsafe { upb_DebugString(msg, mt, Options::NoSortMaps as i32, std::ptr::null_mut(), 0) }; |
||||
assert!(len < isize::MAX as usize); |
||||
// +1 for the trailing NULL
|
||||
let mut buf = vec![0u8; len + 1]; |
||||
// SAFETY:
|
||||
// - `msg` is a legally dereferencable upb_Message whose associated minitable is
|
||||
// `mt`
|
||||
// - `buf` is legally writable for 'buf_len' bytes
|
||||
let written_len = unsafe { |
||||
upb_DebugString(msg, mt, Options::NoSortMaps as i32, buf.as_mut_ptr(), buf.len()) |
||||
}; |
||||
assert_eq!(len, written_len); |
||||
String::from_utf8_lossy(buf.as_slice()).to_string() |
||||
} |
@ -0,0 +1,78 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2024 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/message/internal/iterator.h" // IWYU pragma: keep |
||||
|
||||
#include <stddef.h> |
||||
|
||||
#include "upb/message/accessors.h" |
||||
#include "upb/message/array.h" |
||||
#include "upb/message/internal/accessors.h" |
||||
#include "upb/message/internal/extension.h" |
||||
#include "upb/message/map.h" |
||||
#include "upb/message/message.h" |
||||
#include "upb/mini_table/extension.h" |
||||
#include "upb/mini_table/field.h" |
||||
#include "upb/mini_table/message.h" |
||||
|
||||
// Must be last.
|
||||
#include "upb/port/def.inc" |
||||
|
||||
bool UPB_PRIVATE(_upb_Message_NextBaseField)(const upb_Message* msg, |
||||
const upb_MiniTable* m, |
||||
const upb_MiniTableField** out_f, |
||||
upb_MessageValue* out_v, |
||||
size_t* iter) { |
||||
const size_t count = upb_MiniTable_FieldCount(m); |
||||
size_t i = *iter; |
||||
|
||||
while (++i < count) { |
||||
const upb_MiniTableField* f = upb_MiniTable_GetFieldByIndex(m, i); |
||||
const void* src = UPB_PRIVATE(_upb_Message_DataPtr)(msg, f); |
||||
|
||||
upb_MessageValue val; |
||||
UPB_PRIVATE(_upb_MiniTableField_DataCopy)(f, &val, src); |
||||
|
||||
// Skip field if unset or empty.
|
||||
if (upb_MiniTableField_HasPresence(f)) { |
||||
if (!upb_Message_HasBaseField(msg, f)) continue; |
||||
} else { |
||||
if (UPB_PRIVATE(_upb_MiniTableField_DataIsZero)(f, src)) continue; |
||||
|
||||
if (upb_MiniTableField_IsArray(f)) { |
||||
if (upb_Array_Size(val.array_val) == 0) continue; |
||||
} else if (upb_MiniTableField_IsMap(f)) { |
||||
if (upb_Map_Size(val.map_val) == 0) continue; |
||||
} |
||||
} |
||||
|
||||
*out_f = f; |
||||
*out_v = val; |
||||
*iter = i; |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
bool UPB_PRIVATE(_upb_Message_NextExtension)( |
||||
const upb_Message* msg, const upb_MiniTable* m, |
||||
const upb_MiniTableExtension** out_e, upb_MessageValue* out_v, |
||||
size_t* iter) { |
||||
size_t count; |
||||
const upb_Extension* exts = UPB_PRIVATE(_upb_Message_Getexts)(msg, &count); |
||||
size_t i = *iter; |
||||
|
||||
if (++i < count) { |
||||
*out_e = exts[i].ext; |
||||
*out_v = exts[i].data; |
||||
*iter = i; |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
@ -0,0 +1,35 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifndef THIRD_PARTY_UPB_UPB_MESSAGE_INTERNAL_ITERATOR_H_ |
||||
#define THIRD_PARTY_UPB_UPB_MESSAGE_INTERNAL_ITERATOR_H_ |
||||
|
||||
#include <stddef.h> |
||||
|
||||
#include "upb/message/message.h" |
||||
#include "upb/message/value.h" |
||||
#include "upb/mini_table/extension.h" |
||||
#include "upb/mini_table/field.h" |
||||
#include "upb/mini_table/message.h" |
||||
|
||||
// Must be last.
|
||||
#include "upb/port/def.inc" |
||||
|
||||
#define kUpb_BaseField_Begin ((size_t)-1) |
||||
#define kUpb_Extension_Begin ((size_t)-1) |
||||
|
||||
bool UPB_PRIVATE(_upb_Message_NextBaseField)(const upb_Message* msg, |
||||
const upb_MiniTable* m, |
||||
const upb_MiniTableField** out_f, |
||||
upb_MessageValue* out_v, |
||||
size_t* iter); |
||||
|
||||
bool UPB_PRIVATE(_upb_Message_NextExtension)( |
||||
const upb_Message* msg, const upb_MiniTable* m, |
||||
const upb_MiniTableExtension** out_e, upb_MessageValue* out_v, |
||||
size_t* iter); |
||||
#endif // THIRD_PARTY_UPB_UPB_MESSAGE_INTERNAL_ITERATOR_H_
|
@ -0,0 +1,235 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2024 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/text/debug_string.h" |
||||
|
||||
#include <inttypes.h> |
||||
#include <stdarg.h> |
||||
#include <stddef.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
|
||||
#include "upb/base/descriptor_constants.h" |
||||
#include "upb/message/array.h" |
||||
#include "upb/message/internal/iterator.h" |
||||
#include "upb/message/internal/map_entry.h" |
||||
#include "upb/message/internal/map_sorter.h" |
||||
#include "upb/message/map.h" |
||||
#include "upb/message/message.h" |
||||
#include "upb/message/value.h" |
||||
#include "upb/mini_table/extension.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/text/internal/encode.h" |
||||
#include "upb/wire/eps_copy_input_stream.h" |
||||
|
||||
// Must be last.
|
||||
#include "upb/port/def.inc" |
||||
|
||||
static void _upb_MessageDebugString(txtenc* e, const upb_Message* msg, |
||||
const upb_MiniTable* mt); |
||||
|
||||
static void _upb_FieldDebugString(txtenc* e, upb_MessageValue val, |
||||
const upb_MiniTableField* f, |
||||
const upb_MiniTable* mt, const char* label, |
||||
const upb_MiniTableExtension* ext) { |
||||
UPB_PRIVATE(_upb_TextEncode_Indent)(e); |
||||
const upb_CType ctype = upb_MiniTableField_CType(f); |
||||
const bool is_ext = upb_MiniTableField_IsExtension(f); |
||||
char number[10]; // A 32-bit integer can hold up to 10 digits.
|
||||
snprintf(number, sizeof(number), "%" PRIu32, upb_MiniTableField_Number(f)); |
||||
// label is to pass down whether we're dealing with a "key" of a map or
|
||||
// a "value" of a map.
|
||||
if (!label) label = number; |
||||
|
||||
if (is_ext) { |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "[%s]", label); |
||||
} else { |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "%s", label); |
||||
} |
||||
|
||||
if (ctype == kUpb_CType_Message) { |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, " {"); |
||||
UPB_PRIVATE(_upb_TextEncode_EndField)(e); |
||||
e->indent_depth++; |
||||
const upb_MiniTable* subm = ext ? upb_MiniTableExtension_GetSubMessage(ext) |
||||
: upb_MiniTable_SubMessage(mt, f); |
||||
_upb_MessageDebugString(e, val.msg_val, subm); |
||||
e->indent_depth--; |
||||
UPB_PRIVATE(_upb_TextEncode_Indent)(e); |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "}"); |
||||
UPB_PRIVATE(_upb_TextEncode_EndField)(e); |
||||
return; |
||||
} |
||||
|
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, ": "); |
||||
|
||||
if (ctype == |
||||
kUpb_CType_Enum) { // Enum has to be processed separately because of
|
||||
// divergent behavior between encoders
|
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "%" PRId32, val.int32_val); |
||||
} else { |
||||
UPB_PRIVATE(_upb_TextEncode_Scalar)(e, val, ctype); |
||||
} |
||||
|
||||
UPB_PRIVATE(_upb_TextEncode_EndField)(e); |
||||
} |
||||
|
||||
/*
|
||||
* Arrays print as simple repeated elements, eg. |
||||
* |
||||
* 5: 1 |
||||
* 5: 2 |
||||
* 5: 3 |
||||
*/ |
||||
static void _upb_ArrayDebugString(txtenc* e, const upb_Array* arr, |
||||
const upb_MiniTableField* f, |
||||
const upb_MiniTable* mt, |
||||
const upb_MiniTableExtension* ext) { |
||||
for (size_t i = 0, n = upb_Array_Size(arr); i < n; i++) { |
||||
_upb_FieldDebugString(e, upb_Array_Get(arr, i), f, mt, NULL, ext); |
||||
} |
||||
} |
||||
|
||||
static void _upb_MapEntryDebugString(txtenc* e, upb_MessageValue key, |
||||
upb_MessageValue val, |
||||
const upb_MiniTableField* f, |
||||
const upb_MiniTable* mt) { |
||||
const upb_MiniTable* entry = upb_MiniTable_SubMessage(mt, f); |
||||
const upb_MiniTableField* key_f = upb_MiniTable_MapKey(entry); |
||||
const upb_MiniTableField* val_f = upb_MiniTable_MapValue(entry); |
||||
|
||||
UPB_PRIVATE(_upb_TextEncode_Indent)(e); |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "%u {", upb_MiniTableField_Number(f)); |
||||
UPB_PRIVATE(_upb_TextEncode_EndField)(e); |
||||
e->indent_depth++; |
||||
|
||||
_upb_FieldDebugString(e, key, key_f, entry, "key", NULL); |
||||
_upb_FieldDebugString(e, val, val_f, entry, "value", NULL); |
||||
|
||||
e->indent_depth--; |
||||
UPB_PRIVATE(_upb_TextEncode_Indent)(e); |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "}"); |
||||
UPB_PRIVATE(_upb_TextEncode_EndField)(e); |
||||
} |
||||
|
||||
/*
|
||||
* Maps print as messages of key/value, etc. |
||||
* |
||||
* 1 { |
||||
* key: "abc" |
||||
* value: 123 |
||||
* } |
||||
* 2 { |
||||
* key: "def" |
||||
* value: 456 |
||||
* } |
||||
*/ |
||||
static void _upb_MapDebugString(txtenc* e, const upb_Map* map, |
||||
const upb_MiniTableField* f, |
||||
const upb_MiniTable* mt) { |
||||
if (e->options & UPB_TXTENC_NOSORT) { |
||||
size_t iter = kUpb_Map_Begin; |
||||
upb_MessageValue key, val; |
||||
while (upb_Map_Next(map, &key, &val, &iter)) { |
||||
_upb_MapEntryDebugString(e, key, val, f, mt); |
||||
} |
||||
} else { |
||||
if (upb_Map_Size(map) == 0) return; |
||||
|
||||
const upb_MiniTable* entry = upb_MiniTable_SubMessage(mt, f); |
||||
const upb_MiniTableField* key_f = upb_MiniTable_GetFieldByIndex(entry, 0); |
||||
_upb_sortedmap sorted; |
||||
upb_MapEntry ent; |
||||
|
||||
_upb_mapsorter_pushmap(&e->sorter, upb_MiniTableField_Type(key_f), map, |
||||
&sorted); |
||||
while (_upb_sortedmap_next(&e->sorter, map, &sorted, &ent)) { |
||||
upb_MessageValue key, val; |
||||
memcpy(&key, &ent.k, sizeof(key)); |
||||
memcpy(&val, &ent.v, sizeof(val)); |
||||
_upb_MapEntryDebugString(e, key, val, f, mt); |
||||
} |
||||
_upb_mapsorter_popmap(&e->sorter, &sorted); |
||||
} |
||||
} |
||||
|
||||
static void _upb_MessageDebugString(txtenc* e, const upb_Message* msg, |
||||
const upb_MiniTable* mt) { |
||||
size_t iter = kUpb_BaseField_Begin; |
||||
const upb_MiniTableField* f; |
||||
upb_MessageValue val; |
||||
|
||||
// Base fields will be printed out first, followed by extension fields, and
|
||||
// finally unknown fields.
|
||||
|
||||
while (UPB_PRIVATE(_upb_Message_NextBaseField)(msg, mt, &f, &val, &iter)) { |
||||
if (upb_MiniTableField_IsMap(f)) { |
||||
_upb_MapDebugString(e, val.map_val, f, mt); |
||||
} else if (upb_MiniTableField_IsArray(f)) { |
||||
// ext set to NULL as we're not dealing with extensions yet
|
||||
_upb_ArrayDebugString(e, val.array_val, f, mt, NULL); |
||||
} else { |
||||
// ext set to NULL as we're not dealing with extensions yet
|
||||
// label set to NULL as we're not currently working with a MapEntry
|
||||
_upb_FieldDebugString(e, val, f, mt, NULL, NULL); |
||||
} |
||||
} |
||||
|
||||
const upb_MiniTableExtension* ext; |
||||
upb_MessageValue val_ext; |
||||
iter = kUpb_Extension_Begin; |
||||
while ( |
||||
UPB_PRIVATE(_upb_Message_NextExtension)(msg, mt, &ext, &val_ext, &iter)) { |
||||
const upb_MiniTableField* f = &ext->UPB_PRIVATE(field); |
||||
// It is not sufficient to only pass |f| as we lose valuable information
|
||||
// about sub-messages. It is required that we pass |ext|.
|
||||
if (upb_MiniTableField_IsMap(f)) { |
||||
UPB_UNREACHABLE(); // Maps cannot be extensions.
|
||||
break; |
||||
} else if (upb_MiniTableField_IsArray(f)) { |
||||
_upb_ArrayDebugString(e, val_ext.array_val, f, mt, ext); |
||||
} else { |
||||
// label set to NULL as we're not currently working with a MapEntry
|
||||
_upb_FieldDebugString(e, val_ext, f, mt, NULL, ext); |
||||
} |
||||
} |
||||
|
||||
if ((e->options & UPB_TXTENC_SKIPUNKNOWN) == 0) { |
||||
size_t size; |
||||
const char* ptr = upb_Message_GetUnknown(msg, &size); |
||||
if (size != 0) { |
||||
char* start = e->ptr; |
||||
upb_EpsCopyInputStream stream; |
||||
upb_EpsCopyInputStream_Init(&stream, &ptr, size, true); |
||||
if (!UPB_PRIVATE(_upb_TextEncode_Unknown)(e, ptr, &stream, -1)) { |
||||
/* Unknown failed to parse, back up and don't print it at all. */ |
||||
e->ptr = start; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
size_t upb_DebugString(const upb_Message* msg, const upb_MiniTable* mt, |
||||
int options, char* buf, size_t size) { |
||||
txtenc e; |
||||
|
||||
e.buf = buf; |
||||
e.ptr = buf; |
||||
e.end = UPB_PTRADD(buf, size); |
||||
e.overflow = 0; |
||||
e.indent_depth = 0; |
||||
e.options = options; |
||||
e.ext_pool = NULL; |
||||
_upb_mapsorter_init(&e.sorter); |
||||
|
||||
_upb_MessageDebugString(&e, msg, mt); |
||||
_upb_mapsorter_destroy(&e.sorter); |
||||
return UPB_PRIVATE(_upb_TextEncode_Nullz)(&e, size); |
||||
} |
@ -0,0 +1,42 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifndef UPB_TEXT_ENCODE_DEBUG_H_ |
||||
#define UPB_TEXT_ENCODE_DEBUG_H_ |
||||
|
||||
#include <stddef.h> |
||||
|
||||
#include "upb/message/message.h" |
||||
#include "upb/mini_table/message.h" |
||||
#include "upb/text/options.h" // IWYU pragma: export |
||||
|
||||
// Must be last.
|
||||
#include "upb/port/def.inc" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/* Encodes the given |msg| to a psuedo-text format: Instead of printing field
|
||||
* name to value entries, it will print field number to value entries; much like |
||||
* how unknown fields are printed in upb_TextEncode in this directory's |
||||
* encode.h. |mt| should correspond to the |msg|'s minitable. |
||||
* |
||||
* Output is placed in the given buffer, and always NULL-terminated. The output |
||||
* size (excluding NULL) iss returned. This means that a return value >= |size| |
||||
* implies that the output was truncated. (These are the same semantics as |
||||
* snprintf()). */ |
||||
UPB_API size_t upb_DebugString(const upb_Message* msg, const upb_MiniTable* mt, |
||||
int options, char* buf, size_t size); |
||||
|
||||
#ifdef __cplusplus |
||||
} /* extern "C" */ |
||||
#endif |
||||
|
||||
#include "upb/port/undef.inc" |
||||
|
||||
#endif /* UPB_TEXT_ENCODE_DEBUG_H_ */ |
@ -0,0 +1,63 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2024 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 <stddef.h> |
||||
|
||||
#include <string> |
||||
|
||||
#include <gtest/gtest.h> |
||||
#include "absl/log/absl_log.h" |
||||
#include "upb/base/string_view.h" |
||||
#include "upb/base/upcast.h" |
||||
#include "upb/mem/arena.h" |
||||
#include "upb/message/message.h" |
||||
#include "upb/mini_table/message.h" |
||||
#include "upb/test/test.upb.h" |
||||
#include "upb/test/test.upb_minitable.h" |
||||
#include "upb/text/debug_string.h" |
||||
|
||||
TEST(TextNoReflection, Extensions) { |
||||
const upb_MiniTable* mt_main = upb_0test__ModelWithExtensions_msg_init_ptr; |
||||
upb_Arena* arena = upb_Arena_New(); |
||||
|
||||
upb_test_ModelExtension1* extension1 = upb_test_ModelExtension1_new(arena); |
||||
upb_test_ModelExtension1_set_str(extension1, |
||||
upb_StringView_FromString("Hello")); |
||||
|
||||
upb_test_ModelExtension2* extension2 = upb_test_ModelExtension2_new(arena); |
||||
upb_test_ModelExtension2_set_i(extension2, 5); |
||||
|
||||
upb_test_ModelWithExtensions* msg = upb_test_ModelWithExtensions_new(arena); |
||||
|
||||
upb_test_ModelExtension1_set_model_ext(msg, extension1, arena); |
||||
upb_test_ModelExtension2_set_model_ext(msg, extension2, arena); |
||||
|
||||
// Convert to a type of upb_Message*
|
||||
upb_Message* input = UPB_UPCAST(msg); |
||||
// Resizing/reallocation of the buffer is not necessary since we're only
|
||||
// testing that we get the expected debug string.
|
||||
char* buf = new char[100]; |
||||
int options = |
||||
UPB_TXTENC_NOSORT; // Does not matter, but maps will not be sorted.
|
||||
size_t size = 100; |
||||
size_t real_size = upb_DebugString(input, mt_main, options, buf, size); |
||||
ABSL_LOG(INFO) << "Buffer: \n" |
||||
<< buf << "\n" |
||||
<< "Size:" << real_size << "\n"; |
||||
std::string golden = R"([4135] { |
||||
9: 5 |
||||
} |
||||
[1547] { |
||||
25: "Hello" |
||||
} |
||||
)"; |
||||
ASSERT_EQ(buf[real_size], '\0'); |
||||
std::string str(buf); |
||||
ASSERT_EQ(buf, golden); |
||||
delete[] buf; |
||||
upb_Arena_Free(arena); |
||||
} |
@ -0,0 +1,180 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2024 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/text/internal/encode.h" |
||||
|
||||
#include <inttypes.h> |
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
|
||||
#include "upb/base/descriptor_constants.h" |
||||
#include "upb/base/string_view.h" |
||||
#include "upb/lex/round_trip.h" |
||||
#include "upb/message/array.h" |
||||
#include "upb/wire/eps_copy_input_stream.h" |
||||
#include "upb/wire/reader.h" |
||||
#include "upb/wire/types.h" |
||||
|
||||
// Must be last.
|
||||
#include "upb/port/def.inc" |
||||
|
||||
#define CHK(x) \ |
||||
do { \
|
||||
if (!(x)) { \
|
||||
return false; \
|
||||
} \
|
||||
} while (0) |
||||
|
||||
/*
|
||||
* Unknown fields are printed by number. |
||||
* |
||||
* 1001: 123 |
||||
* 1002: "hello" |
||||
* 1006: 0xdeadbeef |
||||
* 1003: { |
||||
* 1: 111 |
||||
* } |
||||
*/ |
||||
const char* UPB_PRIVATE(_upb_TextEncode_Unknown)(txtenc* e, const char* ptr, |
||||
upb_EpsCopyInputStream* stream, |
||||
int groupnum) { |
||||
// We are guaranteed that the unknown data is valid wire format, and will not
|
||||
// contain tag zero.
|
||||
uint32_t end_group = groupnum > 0 |
||||
? ((groupnum << kUpb_WireReader_WireTypeBits) | |
||||
kUpb_WireType_EndGroup) |
||||
: 0; |
||||
|
||||
while (!upb_EpsCopyInputStream_IsDone(stream, &ptr)) { |
||||
uint32_t tag; |
||||
CHK(ptr = upb_WireReader_ReadTag(ptr, &tag)); |
||||
if (tag == end_group) return ptr; |
||||
|
||||
UPB_PRIVATE(_upb_TextEncode_Indent)(e); |
||||
UPB_PRIVATE(_upb_TextEncode_Printf) |
||||
(e, "%d: ", (int)upb_WireReader_GetFieldNumber(tag)); |
||||
|
||||
switch (upb_WireReader_GetWireType(tag)) { |
||||
case kUpb_WireType_Varint: { |
||||
uint64_t val; |
||||
CHK(ptr = upb_WireReader_ReadVarint(ptr, &val)); |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "%" PRIu64, val); |
||||
break; |
||||
} |
||||
case kUpb_WireType_32Bit: { |
||||
uint32_t val; |
||||
ptr = upb_WireReader_ReadFixed32(ptr, &val); |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "0x%08" PRIu32, val); |
||||
break; |
||||
} |
||||
case kUpb_WireType_64Bit: { |
||||
uint64_t val; |
||||
ptr = upb_WireReader_ReadFixed64(ptr, &val); |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "0x%016" PRIu64, val); |
||||
break; |
||||
} |
||||
case kUpb_WireType_Delimited: { |
||||
int size; |
||||
char* start = e->ptr; |
||||
size_t start_overflow = e->overflow; |
||||
CHK(ptr = upb_WireReader_ReadSize(ptr, &size)); |
||||
CHK(upb_EpsCopyInputStream_CheckDataSizeAvailable(stream, ptr, size)); |
||||
|
||||
// Speculatively try to parse as message.
|
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "{"); |
||||
UPB_PRIVATE(_upb_TextEncode_EndField)(e); |
||||
|
||||
// EpsCopyInputStream can't back up, so create a sub-stream for the
|
||||
// speculative parse.
|
||||
upb_EpsCopyInputStream sub_stream; |
||||
const char* sub_ptr = upb_EpsCopyInputStream_GetAliasedPtr(stream, ptr); |
||||
upb_EpsCopyInputStream_Init(&sub_stream, &sub_ptr, size, true); |
||||
|
||||
e->indent_depth++; |
||||
if (UPB_PRIVATE(_upb_TextEncode_Unknown)(e, sub_ptr, &sub_stream, -1)) { |
||||
ptr = upb_EpsCopyInputStream_Skip(stream, ptr, size); |
||||
e->indent_depth--; |
||||
UPB_PRIVATE(_upb_TextEncode_Indent)(e); |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "}"); |
||||
} else { |
||||
// Didn't work out, print as raw bytes.
|
||||
e->indent_depth--; |
||||
e->ptr = start; |
||||
e->overflow = start_overflow; |
||||
const char* str = ptr; |
||||
ptr = upb_EpsCopyInputStream_ReadString(stream, &str, size, NULL); |
||||
UPB_ASSERT(ptr); |
||||
UPB_PRIVATE(_upb_TextEncode_Bytes) |
||||
(e, (upb_StringView){.data = str, .size = size}); |
||||
} |
||||
break; |
||||
} |
||||
case kUpb_WireType_StartGroup: |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "{"); |
||||
UPB_PRIVATE(_upb_TextEncode_EndField)(e); |
||||
e->indent_depth++; |
||||
CHK(ptr = UPB_PRIVATE(_upb_TextEncode_Unknown)( |
||||
e, ptr, stream, upb_WireReader_GetFieldNumber(tag))); |
||||
e->indent_depth--; |
||||
UPB_PRIVATE(_upb_TextEncode_Indent)(e); |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "}"); |
||||
break; |
||||
default: |
||||
return NULL; |
||||
} |
||||
UPB_PRIVATE(_upb_TextEncode_EndField)(e); |
||||
} |
||||
|
||||
return end_group == 0 && !upb_EpsCopyInputStream_IsError(stream) ? ptr : NULL; |
||||
} |
||||
|
||||
#undef CHK |
||||
|
||||
void UPB_PRIVATE(_upb_TextEncode_Scalar)(txtenc* e, upb_MessageValue val, |
||||
upb_CType ctype) { |
||||
switch (ctype) { |
||||
case kUpb_CType_Bool: |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, val.bool_val ? "true" : "false"); |
||||
break; |
||||
case kUpb_CType_Float: { |
||||
char buf[32]; |
||||
_upb_EncodeRoundTripFloat(val.float_val, buf, sizeof(buf)); |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, buf); |
||||
break; |
||||
} |
||||
case kUpb_CType_Double: { |
||||
char buf[32]; |
||||
_upb_EncodeRoundTripDouble(val.double_val, buf, sizeof(buf)); |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, buf); |
||||
break; |
||||
} |
||||
case kUpb_CType_Int32: |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "%" PRId32, val.int32_val); |
||||
break; |
||||
case kUpb_CType_UInt32: |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "%" PRIu32, val.uint32_val); |
||||
break; |
||||
case kUpb_CType_Int64: |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "%" PRId64, val.int64_val); |
||||
break; |
||||
case kUpb_CType_UInt64: |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "%" PRIu64, val.uint64_val); |
||||
break; |
||||
case kUpb_CType_String: |
||||
UPB_PRIVATE(_upb_HardenedPrintString) |
||||
(e, val.str_val.data, val.str_val.size); |
||||
break; |
||||
case kUpb_CType_Bytes: |
||||
UPB_PRIVATE(_upb_TextEncode_Bytes)(e, val.str_val); |
||||
break; |
||||
case kUpb_CType_Enum: |
||||
UPB_ASSERT(false); // handled separately in each encoder
|
||||
break; |
||||
default: |
||||
UPB_UNREACHABLE(); |
||||
} |
||||
} |
@ -0,0 +1,240 @@ |
||||
// 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
|
||||
|
||||
#ifndef UPB_TEXT_ENCODE_INTERNAL_H_ |
||||
#define UPB_TEXT_ENCODE_INTERNAL_H_ |
||||
|
||||
#include <stdarg.h> |
||||
#include <string.h> |
||||
|
||||
#include "upb/base/descriptor_constants.h" |
||||
#include "upb/base/string_view.h" |
||||
#include "upb/message/array.h" |
||||
#include "upb/message/internal/map_sorter.h" |
||||
#include "upb/port/vsnprintf_compat.h" |
||||
#include "upb/text/options.h" |
||||
#include "upb/wire/eps_copy_input_stream.h" |
||||
#include "utf8_range.h" |
||||
|
||||
// Must be last.
|
||||
#include "upb/port/def.inc" |
||||
|
||||
typedef struct { |
||||
char *buf, *ptr, *end; |
||||
size_t overflow; |
||||
int indent_depth; |
||||
int options; |
||||
const struct upb_DefPool* ext_pool; |
||||
_upb_mapsorter sorter; |
||||
} txtenc; |
||||
|
||||
UPB_INLINE void UPB_PRIVATE(_upb_TextEncode_PutBytes)(txtenc* e, |
||||
const void* data, |
||||
size_t len) { |
||||
size_t have = e->end - e->ptr; |
||||
if (UPB_LIKELY(have >= len)) { |
||||
memcpy(e->ptr, data, len); |
||||
e->ptr += len; |
||||
} else { |
||||
if (have) { |
||||
memcpy(e->ptr, data, have); |
||||
e->ptr += have; |
||||
} |
||||
e->overflow += (len - have); |
||||
} |
||||
} |
||||
|
||||
UPB_INLINE void UPB_PRIVATE(_upb_TextEncode_PutStr)(txtenc* e, |
||||
const char* str) { |
||||
UPB_PRIVATE(_upb_TextEncode_PutBytes)(e, str, strlen(str)); |
||||
} |
||||
|
||||
UPB_INLINE void UPB_PRIVATE(_upb_TextEncode_Printf)(txtenc* e, const char* fmt, |
||||
...) { |
||||
size_t n; |
||||
size_t have = e->end - e->ptr; |
||||
va_list args; |
||||
|
||||
va_start(args, fmt); |
||||
n = _upb_vsnprintf(e->ptr, have, fmt, args); |
||||
va_end(args); |
||||
|
||||
if (UPB_LIKELY(have > n)) { |
||||
e->ptr += n; |
||||
} else { |
||||
e->ptr = UPB_PTRADD(e->ptr, have); |
||||
e->overflow += (n - have); |
||||
} |
||||
} |
||||
|
||||
UPB_INLINE void UPB_PRIVATE(_upb_TextEncode_Indent)(txtenc* e) { |
||||
if ((e->options & UPB_TXTENC_SINGLELINE) == 0) { |
||||
int i = e->indent_depth; |
||||
while (i-- > 0) { |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, " "); |
||||
} |
||||
} |
||||
} |
||||
|
||||
UPB_INLINE void UPB_PRIVATE(_upb_TextEncode_EndField)(txtenc* e) { |
||||
if (e->options & UPB_TXTENC_SINGLELINE) { |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, " "); |
||||
} else { |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\n"); |
||||
} |
||||
} |
||||
|
||||
UPB_INLINE void UPB_PRIVATE(_upb_TextEncode_Escaped)(txtenc* e, |
||||
unsigned char ch) { |
||||
switch (ch) { |
||||
case '\n': |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\\n"); |
||||
break; |
||||
case '\r': |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\\r"); |
||||
break; |
||||
case '\t': |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\\t"); |
||||
break; |
||||
case '\"': |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\\\""); |
||||
break; |
||||
case '\'': |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\\'"); |
||||
break; |
||||
case '\\': |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\\\\"); |
||||
break; |
||||
default: |
||||
UPB_PRIVATE(_upb_TextEncode_Printf)(e, "\\%03o", ch); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// Returns true if `ch` needs to be escaped in TextFormat, independent of any
|
||||
// UTF-8 validity issues.
|
||||
UPB_INLINE bool UPB_PRIVATE(_upb_DefinitelyNeedsEscape)(unsigned char ch) { |
||||
if (ch < 32) return true; |
||||
switch (ch) { |
||||
case '\"': |
||||
case '\'': |
||||
case '\\': |
||||
case 127: |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
UPB_INLINE bool UPB_PRIVATE(_upb_AsciiIsPrint)(unsigned char ch) { |
||||
return ch >= 32 && ch < 127; |
||||
} |
||||
|
||||
// Returns true if this is a high byte that requires UTF-8 validation. If the
|
||||
// UTF-8 validation fails, we must escape the byte.
|
||||
UPB_INLINE bool UPB_PRIVATE(_upb_NeedsUtf8Validation)(unsigned char ch) { |
||||
return ch > 127; |
||||
} |
||||
|
||||
// Returns the number of bytes in the prefix of `val` that do not need escaping.
|
||||
// This is like utf8_range::SpanStructurallyValid(), except that it also
|
||||
// terminates at any ASCII char that needs to be escaped in TextFormat (any char
|
||||
// that has `DefinitelyNeedsEscape(ch) == true`).
|
||||
//
|
||||
// If we could get a variant of utf8_range::SpanStructurallyValid() that could
|
||||
// terminate on any of these chars, that might be more efficient, but it would
|
||||
// be much more complicated to modify that heavily SIMD code.
|
||||
UPB_INLINE size_t UPB_PRIVATE(_SkipPassthroughBytes)(const char* ptr, |
||||
size_t size) { |
||||
for (size_t i = 0; i < size; i++) { |
||||
unsigned char uc = ptr[i]; |
||||
if (UPB_PRIVATE(_upb_DefinitelyNeedsEscape)(uc)) return i; |
||||
if (UPB_PRIVATE(_upb_NeedsUtf8Validation)(uc)) { |
||||
// Find the end of this region of consecutive high bytes, so that we only
|
||||
// give high bytes to the UTF-8 checker. This avoids needing to perform
|
||||
// a second scan of the ASCII characters looking for characters that
|
||||
// need escaping.
|
||||
//
|
||||
// We assume that high bytes are less frequent than plain, printable ASCII
|
||||
// bytes, so we accept the double-scan of high bytes.
|
||||
size_t end = i + 1; |
||||
for (; end < size; end++) { |
||||
if (!UPB_PRIVATE(_upb_NeedsUtf8Validation)(ptr[end])) break; |
||||
} |
||||
size_t n = end - i; |
||||
size_t ok = utf8_range_ValidPrefix(ptr + i, n); |
||||
if (ok != n) return i + ok; |
||||
i += ok - 1; |
||||
} |
||||
} |
||||
return size; |
||||
} |
||||
|
||||
UPB_INLINE void UPB_PRIVATE(_upb_HardenedPrintString)(txtenc* e, |
||||
const char* ptr, |
||||
size_t len) { |
||||
// Print as UTF-8, while guarding against any invalid UTF-8 in the string
|
||||
// field.
|
||||
//
|
||||
// If in the future we have a guaranteed invariant that invalid UTF-8 will
|
||||
// never be present, we could avoid the UTF-8 check here.
|
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\""); |
||||
const char* end = ptr + len; |
||||
while (ptr < end) { |
||||
size_t n = UPB_PRIVATE(_SkipPassthroughBytes)(ptr, end - ptr); |
||||
if (n != 0) { |
||||
UPB_PRIVATE(_upb_TextEncode_PutBytes)(e, ptr, n); |
||||
ptr += n; |
||||
if (ptr == end) break; |
||||
} |
||||
|
||||
// If repeated calls to CEscape() and PrintString() are expensive, we could
|
||||
// consider batching them, at the cost of some complexity.
|
||||
UPB_PRIVATE(_upb_TextEncode_Escaped)(e, *ptr); |
||||
ptr++; |
||||
} |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\""); |
||||
} |
||||
|
||||
UPB_INLINE void UPB_PRIVATE(_upb_TextEncode_Bytes)(txtenc* e, |
||||
upb_StringView data) { |
||||
const char* ptr = data.data; |
||||
const char* end = ptr + data.size; |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\""); |
||||
for (; ptr < end; ptr++) { |
||||
unsigned char uc = *ptr; |
||||
if (UPB_PRIVATE(_upb_AsciiIsPrint)(uc)) { |
||||
UPB_PRIVATE(_upb_TextEncode_PutBytes)(e, ptr, 1); |
||||
} else { |
||||
UPB_PRIVATE(_upb_TextEncode_Escaped)(e, uc); |
||||
} |
||||
} |
||||
UPB_PRIVATE(_upb_TextEncode_PutStr)(e, "\""); |
||||
} |
||||
|
||||
UPB_INLINE size_t UPB_PRIVATE(_upb_TextEncode_Nullz)(txtenc* e, size_t size) { |
||||
size_t ret = e->ptr - e->buf + e->overflow; |
||||
|
||||
if (size > 0) { |
||||
if (e->ptr == e->end) e->ptr--; |
||||
*e->ptr = '\0'; |
||||
} |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
const char* UPB_PRIVATE(_upb_TextEncode_Unknown)(txtenc* e, const char* ptr, |
||||
upb_EpsCopyInputStream* stream, |
||||
int groupnum); |
||||
|
||||
// Must not be called for ctype = kUpb_CType_Enum, as they require different
|
||||
// handling depending on whether or not we're doing reflection-based encoding.
|
||||
void UPB_PRIVATE(_upb_TextEncode_Scalar)(txtenc* e, upb_MessageValue val, |
||||
upb_CType ctype); |
||||
|
||||
#include "upb/port/undef.inc" |
||||
|
||||
#endif // UPB_TEXT_ENCODE_INTERNAL_H_
|
@ -0,0 +1,22 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifndef UPB_TEXT_OPTIONS_H_ |
||||
#define UPB_TEXT_OPTIONS_H_ |
||||
|
||||
enum { |
||||
// When set, prints everything on a single line.
|
||||
UPB_TXTENC_SINGLELINE = 1, |
||||
|
||||
// When set, unknown fields are not printed.
|
||||
UPB_TXTENC_SKIPUNKNOWN = 2, |
||||
|
||||
// When set, maps are *not* sorted (this avoids allocating tmp mem).
|
||||
UPB_TXTENC_NOSORT = 4 |
||||
}; |
||||
|
||||
#endif // UPB_TEXT_OPTIONS_H_
|
Loading…
Reference in new issue