Skip to content

Commit

Permalink
UPB text encoder without using reflection for Rust (used for a messag…
Browse files Browse the repository at this point in the history
…e'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
  • Loading branch information
protobuf-github-bot authored and copybara-github committed Jul 10, 2024
1 parent 32bcf0b commit f9dd9ce
Show file tree
Hide file tree
Showing 22 changed files with 1,256 additions and 466 deletions.
17 changes: 15 additions & 2 deletions rust/test/BUILD
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 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

load(
"//rust:defs.bzl",
"rust_cc_proto_library",
Expand All @@ -15,7 +21,10 @@ UNITTEST_EDITION_TARGET = "//src/google/protobuf:test_protos"
rust_upb_proto_library(
name = "unittest_upb_rust_proto",
testonly = True,
visibility = ["//rust/test/shared:__subpackages__"],
visibility = [
"//rust/test/shared:__subpackages__",
"//rust/test/upb:__subpackages__",
],
deps = [UNITTEST_PROTO_TARGET],
)

Expand Down Expand Up @@ -70,7 +79,10 @@ rust_cc_proto_library(
rust_upb_proto_library(
name = "unittest_edition_upb_rust_proto",
testonly = True,
visibility = ["//rust/test/shared:__subpackages__"],
visibility = [
"//rust/test/shared:__subpackages__",
"//rust/test/upb:__subpackages__",
],
deps = [UNITTEST_EDITION_TARGET],
)

Expand Down Expand Up @@ -379,6 +391,7 @@ rust_upb_proto_library(
testonly = True,
visibility = [
"//rust/test/shared:__subpackages__",
"//rust/test/upb:__subpackages__",
],
deps = ["//src/google/protobuf:map_unittest_proto"],
)
Expand Down
22 changes: 22 additions & 0 deletions rust/test/upb/BUILD
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 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

# Tests specific to upb kernel.
#
# Only add tests that are cpp kernel specific and it is not possible to make them work for upb (
Expand All @@ -13,6 +19,8 @@

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

licenses(["notice"])

# TODO: Enable this for the cpp kernel and move these tests to shared.
rust_test(
name = "string_ctypes_test_upb_test",
Expand All @@ -26,3 +34,17 @@ rust_test(
"@crate_index//:googletest",
],
)

# blaze test //rust/test/upb:debug_string_test --test_arg=--nocapture -c dbg
# --test_output=all to see debug string in test output logs.
rust_test(
name = "debug_string_test",
srcs = ["debug_string_test.rs"],
deps = [
"//rust:protobuf_upb",
"//rust/test:map_unittest_upb_rust_proto",
"//rust/test:unittest_edition_upb_rust_proto",
"//rust/test:unittest_upb_rust_proto",
"@crate_index//:googletest",
],
)
88 changes: 88 additions & 0 deletions rust/test/upb/debug_string_test.rs
Original file line number Diff line number Diff line change
@@ -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()));
}
2 changes: 2 additions & 0 deletions rust/upb/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ rust_library(
"opaque_pointee.rs",
"owned_arena_box.rs",
"string_view.rs",
"text.rs",
"wire.rs",
],
visibility = [
Expand All @@ -48,5 +49,6 @@ cc_library(
"//upb:message_compare",
"//upb:message_copy",
"//upb/mini_table",
"//upb/text:debug",
],
)
3 changes: 3 additions & 0 deletions rust/upb/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,8 @@ pub use owned_arena_box::OwnedArenaBox;
mod string_view;
pub use string_view::StringView;

mod text;
pub use text::debug_string;

pub mod wire;
pub use wire::{upb_Decode, DecodeStatus, EncodeStatus};
66 changes: 66 additions & 0 deletions rust/upb/text.rs
Original file line number Diff line number Diff line change
@@ -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()
}
1 change: 1 addition & 0 deletions rust/upb/upb_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "upb/message/map.h" // IWYU pragma: keep
#include "upb/message/merge.h" // IWYU pragma: keep
#include "upb/mini_table/message.h" // IWYU pragma: keep
#include "upb/text/debug_string.h" // IWYU pragma: keep
// go/keep-sorted end

const size_t __rust_proto_kUpb_Map_Begin = kUpb_Map_Begin;
13 changes: 9 additions & 4 deletions src/google/protobuf/compiler/rust/message.cc
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,16 @@ void MessageDebug(Context& ctx, const Descriptor& msg) {
return;

case Kernel::kUpb:
ctx.Emit({},
ctx.Emit({{"minitable", UpbMinitableName(msg)}},
R"rs(
f.debug_struct(std::any::type_name::<Self>())
.field("raw_msg", &self.raw_msg())
.finish()
let mini_table = unsafe { $std$::ptr::addr_of!($minitable$) };
let string = unsafe {
$pbr$::debug_string(
self.raw_msg(),
mini_table,
)
};
write!(f, "{}", string)
)rs");
return;
}
Expand Down
3 changes: 3 additions & 0 deletions upb/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ upb_amalgamation(
"//upb/lex:lex",
"//upb/mem:internal",
"//upb/message:internal",
"//upb/message:iterator",
"//upb/message:types",
"//upb/mini_descriptor:internal",
"//upb/mini_table:internal",
Expand Down Expand Up @@ -327,6 +328,7 @@ upb_amalgamation(
"//upb/lex:lex",
"//upb/mem:internal",
"//upb/message:internal",
"//upb/message:iterator",
"//upb/message:types",
"//upb/mini_descriptor:internal",
"//upb/mini_table:internal",
Expand Down Expand Up @@ -374,6 +376,7 @@ upb_amalgamation(
"//upb/lex:lex",
"//upb/mem:internal",
"//upb/message:internal",
"//upb/message:iterator",
"//upb/message:types",
"//upb/mini_descriptor:internal",
"//upb/mini_table:internal",
Expand Down
19 changes: 19 additions & 0 deletions upb/message/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ cc_library(
],
)

cc_library(
name = "iterator",
srcs = [
"internal/iterator.c",
],
hdrs = [
"internal/iterator.h",
],
copts = UPB_DEFAULT_COPTS,
visibility = ["//visibility:public"],
deps = [
":internal",
":message",
"//upb:mini_table",
"//upb:port",
],
)

cc_library(
name = "compare",
srcs = [
Expand All @@ -97,6 +115,7 @@ cc_library(
visibility = ["//visibility:public"],
deps = [
":internal",
":iterator",
":message",
"//upb:base",
"//upb:mini_table",
Expand Down
Loading

0 comments on commit f9dd9ce

Please sign in to comment.