Skip to content

Commit f9dd9ce

Browse files
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: 651113583
1 parent 32bcf0b commit f9dd9ce

File tree

22 files changed

+1256
-466
lines changed

22 files changed

+1256
-466
lines changed

rust/test/BUILD

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Protocol Buffers - Google's data interchange format
2+
# Copyright 2023 Google LLC. All rights reserved.
3+
# Use of this source code is governed by a BSD-style
4+
# license that can be found in the LICENSE file or at
5+
# https://developers.google.com/open-source/licenses/bsd
6+
17
load(
28
"//rust:defs.bzl",
39
"rust_cc_proto_library",
@@ -15,7 +21,10 @@ UNITTEST_EDITION_TARGET = "//src/google/protobuf:test_protos"
1521
rust_upb_proto_library(
1622
name = "unittest_upb_rust_proto",
1723
testonly = True,
18-
visibility = ["//rust/test/shared:__subpackages__"],
24+
visibility = [
25+
"//rust/test/shared:__subpackages__",
26+
"//rust/test/upb:__subpackages__",
27+
],
1928
deps = [UNITTEST_PROTO_TARGET],
2029
)
2130

@@ -70,7 +79,10 @@ rust_cc_proto_library(
7079
rust_upb_proto_library(
7180
name = "unittest_edition_upb_rust_proto",
7281
testonly = True,
73-
visibility = ["//rust/test/shared:__subpackages__"],
82+
visibility = [
83+
"//rust/test/shared:__subpackages__",
84+
"//rust/test/upb:__subpackages__",
85+
],
7486
deps = [UNITTEST_EDITION_TARGET],
7587
)
7688

@@ -379,6 +391,7 @@ rust_upb_proto_library(
379391
testonly = True,
380392
visibility = [
381393
"//rust/test/shared:__subpackages__",
394+
"//rust/test/upb:__subpackages__",
382395
],
383396
deps = ["//src/google/protobuf:map_unittest_proto"],
384397
)

rust/test/upb/BUILD

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Protocol Buffers - Google's data interchange format
2+
# Copyright 2023 Google LLC. All rights reserved.
3+
# Use of this source code is governed by a BSD-style
4+
# license that can be found in the LICENSE file or at
5+
# https://developers.google.com/open-source/licenses/bsd
6+
17
# Tests specific to upb kernel.
28
#
39
# Only add tests that are cpp kernel specific and it is not possible to make them work for upb (
@@ -13,6 +19,8 @@
1319

1420
load("@rules_rust//rust:defs.bzl", "rust_test")
1521

22+
licenses(["notice"])
23+
1624
# TODO: Enable this for the cpp kernel and move these tests to shared.
1725
rust_test(
1826
name = "string_ctypes_test_upb_test",
@@ -26,3 +34,17 @@ rust_test(
2634
"@crate_index//:googletest",
2735
],
2836
)
37+
38+
# blaze test //rust/test/upb:debug_string_test --test_arg=--nocapture -c dbg
39+
# --test_output=all to see debug string in test output logs.
40+
rust_test(
41+
name = "debug_string_test",
42+
srcs = ["debug_string_test.rs"],
43+
deps = [
44+
"//rust:protobuf_upb",
45+
"//rust/test:map_unittest_upb_rust_proto",
46+
"//rust/test:unittest_edition_upb_rust_proto",
47+
"//rust/test:unittest_upb_rust_proto",
48+
"@crate_index//:googletest",
49+
],
50+
)

rust/test/upb/debug_string_test.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Protocol Buffers - Google's data interchange format
2+
// Copyright 2024 Google LLC. All rights reserved.
3+
//
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file or at
6+
// https://developers.google.com/open-source/licenses/bsd
7+
8+
use googletest::prelude::*;
9+
use map_unittest_rust_proto::TestMapWithMessages;
10+
use protobuf_upb::proto;
11+
use unittest_rust_proto::{
12+
test_all_types::NestedEnum as NestedEnumProto2,
13+
test_all_types::NestedMessage as NestedMessageProto2, TestAllTypes as TestAllTypesProto2,
14+
};
15+
16+
#[test]
17+
fn test_debug_string() {
18+
let mut msg = proto!(TestAllTypesProto2 {
19+
optional_int32: 42,
20+
optional_string: "Hello World",
21+
optional_nested_enum: NestedEnumProto2::Bar,
22+
oneof_uint32: 452235,
23+
optional_nested_message: proto!(NestedMessageProto2 { bb: 100 }),
24+
});
25+
let mut repeated_string = msg.repeated_string_mut();
26+
repeated_string.push("Hello World");
27+
repeated_string.push("Hello World");
28+
repeated_string.push("Hello World");
29+
30+
let mut msg_map = TestMapWithMessages::new();
31+
println!("EMPTY MSG: {:?}", msg_map); // Make sure that we can print an empty message.
32+
msg_map.map_string_all_types_mut().insert("hello", msg.as_view());
33+
msg_map.map_string_all_types_mut().insert("fizz", msg.as_view());
34+
msg_map.map_string_all_types_mut().insert("boo", msg.as_view());
35+
36+
println!("{:?}", msg_map);
37+
println!("{:?}", msg_map.as_view()); // Make sure that we can print as_view
38+
println!("{:?}", msg_map.as_mut()); // Make sure that we can print as_mut
39+
let golden = r#"12 {
40+
key: "hello"
41+
value {
42+
1: 42
43+
14: "Hello World"
44+
18 {
45+
1: 100
46+
}
47+
21: 2
48+
44: "Hello World"
49+
44: "Hello World"
50+
44: "Hello World"
51+
111: 452235
52+
}
53+
}
54+
12 {
55+
key: "fizz"
56+
value {
57+
1: 42
58+
14: "Hello World"
59+
18 {
60+
1: 100
61+
}
62+
21: 2
63+
44: "Hello World"
64+
44: "Hello World"
65+
44: "Hello World"
66+
111: 452235
67+
}
68+
}
69+
12 {
70+
key: "boo"
71+
value {
72+
1: 42
73+
14: "Hello World"
74+
18 {
75+
1: 100
76+
}
77+
21: 2
78+
44: "Hello World"
79+
44: "Hello World"
80+
44: "Hello World"
81+
111: 452235
82+
}
83+
}
84+
"#;
85+
// C strings are null terminated while Rust strings are not.
86+
let null_terminated_str = format!("{}\0", golden);
87+
assert_that!(format!("{:?}", msg_map), eq(null_terminated_str.as_str()));
88+
}

rust/upb/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ rust_library(
2525
"opaque_pointee.rs",
2626
"owned_arena_box.rs",
2727
"string_view.rs",
28+
"text.rs",
2829
"wire.rs",
2930
],
3031
visibility = [
@@ -48,5 +49,6 @@ cc_library(
4849
"//upb:message_compare",
4950
"//upb:message_copy",
5051
"//upb/mini_table",
52+
"//upb/text:debug",
5153
],
5254
)

rust/upb/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,8 @@ pub use owned_arena_box::OwnedArenaBox;
5050
mod string_view;
5151
pub use string_view::StringView;
5252

53+
mod text;
54+
pub use text::debug_string;
55+
5356
pub mod wire;
5457
pub use wire::{upb_Decode, DecodeStatus, EncodeStatus};

rust/upb/text.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Protocol Buffers - Google's data interchange format
2+
// Copyright 2024 Google LLC. All rights reserved.
3+
//
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file or at
6+
// https://developers.google.com/open-source/licenses/bsd
7+
8+
use crate::{upb_MiniTable, RawMessage};
9+
10+
extern "C" {
11+
/// Returns the minimum needed length (excluding NULL) that `buf` has to be
12+
/// to hold the `msg`s debug string.
13+
///
14+
/// SAFETY:
15+
/// - `msg` is pointing at a valid upb_Message with associated minitable
16+
/// `mt`
17+
/// - `buf` is legally writable for `size` bytes (`buf` may be nullptr if
18+
/// `size` is 0)
19+
fn upb_DebugString(
20+
msg: RawMessage,
21+
mt: *const upb_MiniTable,
22+
options: i32,
23+
buf: *mut u8,
24+
size: usize,
25+
) -> usize;
26+
}
27+
28+
#[allow(dead_code)]
29+
#[repr(i32)]
30+
enum Options {
31+
// When set, prints everything on a single line.
32+
SingleLine = 1,
33+
34+
// When set, unknown fields are not printed.
35+
SkipUnknown = 2,
36+
37+
// When set, maps are *not* sorted (this avoids allocating tmp mem).
38+
NoSortMaps = 4,
39+
}
40+
41+
/// Returns a string of field number to value entries of a message.
42+
///
43+
/// # Safety
44+
/// - `mt` must correspond to the `msg`s minitable.
45+
pub unsafe fn debug_string(msg: RawMessage, mt: *const upb_MiniTable) -> String {
46+
// Only find out the length first to then allocate a buffer of the minimum size
47+
// needed.
48+
// SAFETY:
49+
// - `msg` is a legally dereferencable upb_Message whose associated minitable is
50+
// `mt`
51+
// - `buf` is nullptr and `buf_len` is 0
52+
let len =
53+
unsafe { upb_DebugString(msg, mt, Options::NoSortMaps as i32, std::ptr::null_mut(), 0) };
54+
assert!(len < isize::MAX as usize);
55+
// +1 for the trailing NULL
56+
let mut buf = vec![0u8; len + 1];
57+
// SAFETY:
58+
// - `msg` is a legally dereferencable upb_Message whose associated minitable is
59+
// `mt`
60+
// - `buf` is legally writable for 'buf_len' bytes
61+
let written_len = unsafe {
62+
upb_DebugString(msg, mt, Options::NoSortMaps as i32, buf.as_mut_ptr(), buf.len())
63+
};
64+
assert_eq!(len, written_len);
65+
String::from_utf8_lossy(buf.as_slice()).to_string()
66+
}

rust/upb/upb_api.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "upb/message/map.h" // IWYU pragma: keep
2020
#include "upb/message/merge.h" // IWYU pragma: keep
2121
#include "upb/mini_table/message.h" // IWYU pragma: keep
22+
#include "upb/text/debug_string.h" // IWYU pragma: keep
2223
// go/keep-sorted end
2324

2425
const size_t __rust_proto_kUpb_Map_Begin = kUpb_Map_Begin;

src/google/protobuf/compiler/rust/message.cc

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,16 @@ void MessageDebug(Context& ctx, const Descriptor& msg) {
156156
return;
157157

158158
case Kernel::kUpb:
159-
ctx.Emit({},
159+
ctx.Emit({{"minitable", UpbMinitableName(msg)}},
160160
R"rs(
161-
f.debug_struct(std::any::type_name::<Self>())
162-
.field("raw_msg", &self.raw_msg())
163-
.finish()
161+
let mini_table = unsafe { $std$::ptr::addr_of!($minitable$) };
162+
let string = unsafe {
163+
$pbr$::debug_string(
164+
self.raw_msg(),
165+
mini_table,
166+
)
167+
};
168+
write!(f, "{}", string)
164169
)rs");
165170
return;
166171
}

upb/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ upb_amalgamation(
281281
"//upb/lex:lex",
282282
"//upb/mem:internal",
283283
"//upb/message:internal",
284+
"//upb/message:iterator",
284285
"//upb/message:types",
285286
"//upb/mini_descriptor:internal",
286287
"//upb/mini_table:internal",
@@ -327,6 +328,7 @@ upb_amalgamation(
327328
"//upb/lex:lex",
328329
"//upb/mem:internal",
329330
"//upb/message:internal",
331+
"//upb/message:iterator",
330332
"//upb/message:types",
331333
"//upb/mini_descriptor:internal",
332334
"//upb/mini_table:internal",
@@ -374,6 +376,7 @@ upb_amalgamation(
374376
"//upb/lex:lex",
375377
"//upb/mem:internal",
376378
"//upb/message:internal",
379+
"//upb/message:iterator",
377380
"//upb/message:types",
378381
"//upb/mini_descriptor:internal",
379382
"//upb/mini_table:internal",

upb/message/BUILD

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,24 @@ cc_library(
8585
],
8686
)
8787

88+
cc_library(
89+
name = "iterator",
90+
srcs = [
91+
"internal/iterator.c",
92+
],
93+
hdrs = [
94+
"internal/iterator.h",
95+
],
96+
copts = UPB_DEFAULT_COPTS,
97+
visibility = ["//visibility:public"],
98+
deps = [
99+
":internal",
100+
":message",
101+
"//upb:mini_table",
102+
"//upb:port",
103+
],
104+
)
105+
88106
cc_library(
89107
name = "compare",
90108
srcs = [
@@ -97,6 +115,7 @@ cc_library(
97115
visibility = ["//visibility:public"],
98116
deps = [
99117
":internal",
118+
":iterator",
100119
":message",
101120
"//upb:base",
102121
"//upb:mini_table",

0 commit comments

Comments
 (0)