blob: b4e644b21f34eb73162e74e4b0cf1c2aa8095cc6 [file] [log] [blame]
// 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
#include "upb/util/def_to_proto.h"
#include <cstddef>
#include <memory>
#include <string>
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/descriptor.upb.h"
#include "google/protobuf/descriptor.upbdefs.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "google/protobuf/dynamic_message.h"
#include "google/protobuf/util/message_differencer.h"
#include "upb/base/string_view.h"
#include "upb/mem/arena.hpp"
#include "upb/reflection/def.hpp"
#include "upb/test/parse_text_proto.h"
#include "upb/util/def_to_proto_editions_test.upbdefs.h"
#include "upb/util/def_to_proto_test.h"
#include "upb/util/def_to_proto_test.upbdefs.h"
namespace upb_test {
// Loads and retrieves a descriptor for `msgdef` into the given `pool`.
const google::protobuf::Descriptor* AddMessageDescriptor(
upb::MessageDefPtr msgdef, google::protobuf::DescriptorPool* pool) {
upb::Arena tmp_arena;
upb::FileDefPtr file = msgdef.file();
google_protobuf_FileDescriptorProto* upb_proto =
upb_FileDef_ToProto(file.ptr(), tmp_arena.ptr());
size_t size;
const char* buf = google_protobuf_FileDescriptorProto_serialize(
upb_proto, tmp_arena.ptr(), &size);
google::protobuf::FileDescriptorProto google_proto;
google_proto.ParseFromArray(buf, size);
const google::protobuf::FileDescriptor* file_desc =
pool->BuildFile(google_proto);
EXPECT_TRUE(file_desc != nullptr);
return pool->FindMessageTypeByName(msgdef.full_name());
}
// Converts a upb `msg` (with type `msgdef`) into a protobuf Message object from
// the given factory and descriptor.
std::unique_ptr<google::protobuf::Message> ToProto(
const upb_Message* msg, const upb_MessageDef* msgdef,
const google::protobuf::Descriptor* desc,
google::protobuf::MessageFactory* factory) {
upb::Arena arena;
EXPECT_TRUE(desc != nullptr);
std::unique_ptr<google::protobuf::Message> google_msg(
factory->GetPrototype(desc)->New());
char* buf;
size_t size;
upb_EncodeStatus status = upb_Encode(msg, upb_MessageDef_MiniTable(msgdef), 0,
arena.ptr(), &buf, &size);
EXPECT_EQ(status, kUpb_EncodeStatus_Ok);
google_msg->ParseFromArray(buf, size);
return google_msg;
}
// A gtest matcher that verifies that a proto is equal to `proto`. Both `proto`
// and `arg` must be messages of type `msgdef_func` (a .upbdefs.h function that
// loads a known msgdef into the given defpool).
MATCHER_P2(EqualsUpbProto, proto, msgdef_func,
negation ? "are not equal" : "are equal") {
upb::DefPool defpool;
google::protobuf::DescriptorPool pool;
google::protobuf::DynamicMessageFactory factory;
upb::MessageDefPtr msgdef(msgdef_func(defpool.ptr()));
EXPECT_TRUE(msgdef.ptr() != nullptr);
const google::protobuf::Descriptor* desc =
AddMessageDescriptor(msgdef, &pool);
EXPECT_TRUE(desc != nullptr);
std::unique_ptr<google::protobuf::Message> m1(
ToProto(UPB_UPCAST(proto), msgdef.ptr(), desc, &factory));
std::unique_ptr<google::protobuf::Message> m2(
ToProto((upb_Message*)arg, msgdef.ptr(), desc, &factory));
std::string differences;
google::protobuf::util::MessageDifferencer differencer;
differencer.ReportDifferencesToString(&differences);
bool eq = differencer.Compare(*m2, *m1);
if (!eq) {
*result_listener << differences;
}
return eq;
}
// Verifies that the given upb FileDef can be converted to a proto that matches
// `proto`.
void CheckFile(const upb::FileDefPtr file,
const google_protobuf_FileDescriptorProto* proto) {
upb::Arena arena;
google_protobuf_FileDescriptorProto* proto2 =
upb_FileDef_ToProto(file.ptr(), arena.ptr());
ASSERT_THAT(
proto,
EqualsUpbProto(proto2, google_protobuf_FileDescriptorProto_getmsgdef));
}
// Verifies that upb/util/def_to_proto_test.proto can round-trip:
// serialized descriptor -> upb def -> serialized descriptor
TEST(DefToProto, Test) {
upb::Arena arena;
upb::DefPool defpool;
upb_StringView test_file_desc =
upb_util_def_to_proto_test_proto_upbdefinit.descriptor;
const auto* file_desc = google_protobuf_FileDescriptorProto_parse(
test_file_desc.data, test_file_desc.size, arena.ptr());
upb::MessageDefPtr msgdef(pkg_Message_getmsgdef(defpool.ptr()));
upb::FileDefPtr file = msgdef.file();
CheckFile(file, file_desc);
}
// Verifies that editions don't leak out legacy feature APIs (e.g. TYPE_GROUP
// and LABEL_REQUIRED):
// serialized descriptor -> upb def -> serialized descriptor
TEST(DefToProto, TestEditionsLegacyFeatures) {
upb::Arena arena;
upb::DefPool defpool;
upb_StringView test_file_desc =
upb_util_def_to_proto_editions_test_proto_upbdefinit
.descriptor;
const auto* file = google_protobuf_FileDescriptorProto_parse(
test_file_desc.data, test_file_desc.size, arena.ptr());
size_t size;
const auto* messages = google_protobuf_FileDescriptorProto_message_type(file, &size);
ASSERT_EQ(size, 1);
const auto* fields = google_protobuf_DescriptorProto_field(messages[0], &size);
ASSERT_EQ(size, 2);
EXPECT_EQ(google_protobuf_FieldDescriptorProto_label(fields[0]),
google_protobuf_FieldDescriptorProto_LABEL_OPTIONAL);
EXPECT_EQ(google_protobuf_FieldDescriptorProto_type(fields[1]),
google_protobuf_FieldDescriptorProto_TYPE_MESSAGE);
}
// Like the previous test, but uses a message layout built at runtime.
TEST(DefToProto, TestRuntimeReflection) {
upb::Arena arena;
upb::DefPool defpool;
upb_StringView test_file_desc =
upb_util_def_to_proto_test_proto_upbdefinit.descriptor;
const auto* file_desc = google_protobuf_FileDescriptorProto_parse(
test_file_desc.data, test_file_desc.size, arena.ptr());
_upb_DefPool_LoadDefInitEx(
defpool.ptr(),
&upb_util_def_to_proto_test_proto_upbdefinit, true);
upb::FileDefPtr file = defpool.FindFileByName(
upb_util_def_to_proto_test_proto_upbdefinit.filename);
CheckFile(file, file_desc);
}
// Fuzz test regressions.
TEST(FuzzTest, EmptyPackage) {
RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { package: "" })pb"));
}
TEST(FuzzTest, EmptyName) {
RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { name: "" })pb"));
}
TEST(FuzzTest, EmptyPackage2) {
RoundTripDescriptor(
ParseTextProtoOrDie(R"pb(file { name: "n" package: "" })pb"));
}
TEST(FuzzTest, FileNameEmbeddedNull) {
RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file { name: "\000" })pb"));
}
TEST(FuzzTest, DuplicateOneofIndex) {
RoundTripDescriptor(ParseTextProtoOrDie(
R"pb(file {
name: "F"
message_type {
name: "M"
oneof_decl { name: "O" }
field { name: "f1" number: 1 type: TYPE_INT32 oneof_index: 0 }
field { name: "f2" number: 1 type: TYPE_INT32 oneof_index: 0 }
}
})pb"));
}
TEST(FuzzTest, NanValue) {
RoundTripDescriptor(ParseTextProtoOrDie(
R"pb(file {
enum_type {
value {
number: 0
options { uninterpreted_option { double_value: nan } }
}
}
})pb"));
}
TEST(FuzzTest, EnumValueEmbeddedNull) {
RoundTripDescriptor(ParseTextProtoOrDie(
R"pb(file {
name: "\035"
enum_type {
name: "f"
value { name: "\000" number: 0 }
}
})pb"));
}
TEST(FuzzTest, EnumValueNoNumber) {
RoundTripDescriptor(ParseTextProtoOrDie(
R"pb(file {
name: "\035"
enum_type {
name: "f"
value { name: "abc" }
}
})pb"));
}
TEST(FuzzTest, DefaultWithUnterminatedHex) {
RoundTripDescriptor(ParseTextProtoOrDie(
R"pb(file {
name: "\035"
message_type {
name: "A"
field {
name: "f"
number: 1
label: LABEL_OPTIONAL
type: TYPE_BYTES
default_value: "\\x"
}
}
})pb"));
}
TEST(FuzzTest, DefaultWithValidHexEscape) {
RoundTripDescriptor(ParseTextProtoOrDie(
R"pb(file {
name: "\035"
message_type {
name: "A"
field {
name: "f"
number: 1
label: LABEL_OPTIONAL
type: TYPE_BYTES
default_value: "\\x03"
}
}
})pb"));
}
TEST(FuzzTest, DefaultWithValidHexEscapePrintable) {
RoundTripDescriptor(ParseTextProtoOrDie(
R"pb(file {
name: "\035"
message_type {
name: "A"
field {
name: "f"
number: 1
label: LABEL_OPTIONAL
type: TYPE_BYTES
default_value: "\\x23" # 0x32 = '#'
}
}
})pb"));
}
TEST(FuzzTest, PackageStartsWithNumber) {
RoundTripDescriptor(
ParseTextProtoOrDie(R"pb(file { name: "" package: "0" })pb"));
}
TEST(FuzzTest, RoundTripDescriptorRegression) {
RoundTripDescriptor(ParseTextProtoOrDie(R"pb(file {
name: ""
message_type {
name: "A"
field {
name: "B"
number: 1
type: TYPE_BYTES
default_value: "\007"
}
}
})pb"));
}
// Multiple oneof fields which have the same name.
TEST(FuzzTest, RoundTripDescriptorRegressionOneofSameName) {
RoundTripDescriptor(ParseTextProtoOrDie(
R"pb(file {
name: "N"
package: ""
message_type {
name: "b"
field { name: "W" number: 1 type: TYPE_BYTES oneof_index: 0 }
field { name: "W" number: 17 type: TYPE_UINT32 oneof_index: 0 }
oneof_decl { name: "k" }
}
})pb"));
}
TEST(FuzzTest, NegativeOneofIndex) {
RoundTripDescriptor(ParseTextProtoOrDie(
R"pb(file {
message_type {
name: "A"
field { name: "A" number: 0 type_name: "" oneof_index: -1 }
}
}
)pb"));
}
} // namespace upb_test