Skip to content

Commit 0bc1f14

Browse files
C++Proto JSON: Align behavior on the type.proto path with the descriptor.proto path
- When printing maps to JSON, if either a key or value wasn't present on the binary wire repr, the type.proto path crashed. It now properly uses the default value for the type. - Multiple instances on the binary wire format for the same singular value are rejected in the descriptor.proto case, versus in the type.proto case it could silently used the first seen value. The second case is odd since normal wire format behavior spec is last value silently wins (= neither of the preexisting behaviors here). This change locally aligns the type.proto path to match the descriptor.proto path. Fixes #26885 PiperOrigin-RevId: 900807575
1 parent fccfcbf commit 0bc1f14

3 files changed

Lines changed: 253 additions & 16 deletions

File tree

src/google/protobuf/json/internal/unparser_traits.h

Lines changed: 142 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@
1515
#include <vector>
1616

1717
#include "google/protobuf/type.pb.h"
18-
#include "absl/container/flat_hash_map.h"
1918
#include "absl/status/status.h"
2019
#include "absl/status/statusor.h"
2120
#include "absl/strings/escaping.h"
2221
#include "absl/strings/numbers.h"
2322
#include "absl/strings/str_cat.h"
24-
#include "absl/strings/str_format.h"
2523
#include "absl/strings/string_view.h"
2624
#include "google/protobuf/dynamic_message.h"
2725
#include "google/protobuf/io/coded_stream.h"
@@ -341,54 +339,182 @@ struct UnparseProto3Type : Proto3Type {
341339
return absl::InternalError("message fields cannot have defaults");
342340
}
343341

344-
static absl::StatusOr<float> GetFloat(Field f, const Msg& msg,
345-
size_t idx = 0) {
342+
static absl::StatusOr<float> GetFloat(Field f, const Msg& msg) {
343+
auto span = msg.Get<float>(f->proto().number());
344+
if (span.empty()) {
345+
return GetFloat(f);
346+
}
347+
if (span.size() > 1) {
348+
return absl::InternalError(absl::StrCat(
349+
"Attempted to read a non-repeated field with multiple values: ",
350+
f->parent().proto().name()));
351+
}
352+
return span[0];
353+
}
354+
355+
static absl::StatusOr<float> GetFloat(Field f, const Msg& msg, size_t idx) {
346356
return msg.Get<float>(f->proto().number())[idx];
347357
}
348358

349-
static absl::StatusOr<double> GetDouble(Field f, const Msg& msg,
350-
size_t idx = 0) {
359+
static absl::StatusOr<double> GetDouble(Field f, const Msg& msg) {
360+
auto span = msg.Get<double>(f->proto().number());
361+
if (span.empty()) {
362+
return GetDouble(f);
363+
}
364+
if (span.size() > 1) {
365+
return absl::InternalError(absl::StrCat(
366+
"Attempted to read a non-repeated field with multiple values: ",
367+
f->parent().proto().name()));
368+
}
369+
return span[0];
370+
}
371+
372+
static absl::StatusOr<double> GetDouble(Field f, const Msg& msg, size_t idx) {
351373
return msg.Get<double>(f->proto().number())[idx];
352374
}
353375

354-
static absl::StatusOr<int32_t> GetInt32(Field f, const Msg& msg,
355-
size_t idx = 0) {
376+
static absl::StatusOr<int32_t> GetInt32(Field f, const Msg& msg) {
377+
auto span = msg.Get<int32_t>(f->proto().number());
378+
if (span.empty()) {
379+
return GetInt32(f);
380+
}
381+
if (span.size() > 1) {
382+
return absl::InternalError(absl::StrCat(
383+
"Attempted to read a non-repeated field with multiple values: ",
384+
f->parent().proto().name()));
385+
}
386+
return span[0];
387+
}
388+
389+
static absl::StatusOr<int32_t> GetInt32(Field f, const Msg& msg, size_t idx) {
356390
return msg.Get<int32_t>(f->proto().number())[idx];
357391
}
358392

393+
static absl::StatusOr<uint32_t> GetUInt32(Field f, const Msg& msg) {
394+
auto span = msg.Get<uint32_t>(f->proto().number());
395+
if (span.empty()) {
396+
return GetUInt32(f);
397+
}
398+
if (span.size() > 1) {
399+
return absl::InternalError(absl::StrCat(
400+
"Attempted to read a non-repeated field with multiple values: ",
401+
f->parent().proto().name()));
402+
}
403+
return span[0];
404+
}
405+
359406
static absl::StatusOr<uint32_t> GetUInt32(Field f, const Msg& msg,
360-
size_t idx = 0) {
407+
size_t idx) {
361408
return msg.Get<uint32_t>(f->proto().number())[idx];
362409
}
363410

364-
static absl::StatusOr<int64_t> GetInt64(Field f, const Msg& msg,
365-
size_t idx = 0) {
411+
static absl::StatusOr<int64_t> GetInt64(Field f, const Msg& msg) {
412+
auto span = msg.Get<int64_t>(f->proto().number());
413+
if (span.empty()) {
414+
return GetInt64(f);
415+
}
416+
if (span.size() > 1) {
417+
return absl::InternalError(absl::StrCat(
418+
"Attempted to read a non-repeated field with multiple values: ",
419+
f->parent().proto().name()));
420+
}
421+
return span[0];
422+
}
423+
424+
static absl::StatusOr<int64_t> GetInt64(Field f, const Msg& msg, size_t idx) {
366425
return msg.Get<int64_t>(f->proto().number())[idx];
367426
}
368427

428+
static absl::StatusOr<uint64_t> GetUInt64(Field f, const Msg& msg) {
429+
auto span = msg.Get<uint64_t>(f->proto().number());
430+
if (span.empty()) {
431+
return GetUInt64(f);
432+
}
433+
if (span.size() > 1) {
434+
return absl::InternalError(absl::StrCat(
435+
"Attempted to read a non-repeated field with multiple values: ",
436+
f->parent().proto().name()));
437+
}
438+
return span[0];
439+
}
440+
369441
static absl::StatusOr<uint64_t> GetUInt64(Field f, const Msg& msg,
370-
size_t idx = 0) {
442+
size_t idx) {
371443
return msg.Get<uint64_t>(f->proto().number())[idx];
372444
}
373445

374-
static absl::StatusOr<bool> GetBool(Field f, const Msg& msg, size_t idx = 0) {
446+
static absl::StatusOr<bool> GetBool(Field f, const Msg& msg) {
447+
auto span = msg.Get<Msg::Bool>(f->proto().number());
448+
if (span.empty()) {
449+
return GetBool(f);
450+
}
451+
if (span.size() > 1) {
452+
return absl::InternalError(absl::StrCat(
453+
"Attempted to read a non-repeated field with multiple values: ",
454+
f->parent().proto().name()));
455+
}
456+
return span[0] == Msg::kTrue;
457+
}
458+
459+
static absl::StatusOr<bool> GetBool(Field f, const Msg& msg, size_t idx) {
375460
return msg.Get<Msg::Bool>(f->proto().number())[idx] == Msg::kTrue;
376461
}
377462

463+
static absl::StatusOr<int32_t> GetEnumValue(Field f, const Msg& msg) {
464+
auto span = msg.Get<int32_t>(f->proto().number());
465+
if (span.empty()) {
466+
return GetEnumValue(f);
467+
}
468+
if (span.size() > 1) {
469+
return absl::InternalError(absl::StrCat(
470+
"Attempted to read a non-repeated field with multiple values: ",
471+
f->parent().proto().name()));
472+
}
473+
return span[0];
474+
}
475+
378476
static absl::StatusOr<int32_t> GetEnumValue(Field f, const Msg& msg,
379-
size_t idx = 0) {
477+
size_t idx) {
380478
return msg.Get<int32_t>(f->proto().number())[idx];
381479
}
382480

481+
static absl::StatusOr<absl::string_view> GetString(Field f,
482+
std::string& scratch,
483+
const Msg& msg) {
484+
auto span = msg.Get<std::string>(f->proto().number());
485+
if (span.empty()) {
486+
return GetString(f, scratch);
487+
}
488+
if (span.size() > 1) {
489+
return absl::InternalError(absl::StrCat(
490+
"Attempted to read a non-repeated field with multiple values: ",
491+
f->parent().proto().name()));
492+
}
493+
return span[0];
494+
}
495+
383496
static absl::StatusOr<absl::string_view> GetString(Field f,
384497
std::string& scratch,
385498
const Msg& msg,
386-
size_t idx = 0) {
499+
size_t idx) {
387500
return msg.Get<std::string>(f->proto().number())[idx];
388501
}
389502

503+
static absl::StatusOr<const Msg*> GetMessage(Field f, const Msg& msg) {
504+
auto span = msg.Get<Msg>(f->proto().number());
505+
if (span.empty()) {
506+
return GetMessage(f);
507+
}
508+
if (span.size() > 1) {
509+
return absl::InternalError(absl::StrCat(
510+
"Attempted to read a non-repeated field with multiple values: ",
511+
f->parent().proto().name()));
512+
}
513+
return &span[0];
514+
}
515+
390516
static absl::StatusOr<const Msg*> GetMessage(Field f, const Msg& msg,
391-
size_t idx = 0) {
517+
size_t idx) {
392518
return &msg.Get<Msg>(f->proto().number())[idx];
393519
}
394520

src/google/protobuf/json/json_test.cc

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ using ::proto3::TestEnumValue;
4747
using ::proto3::TestMap;
4848
using ::proto3::TestMessage;
4949
using ::proto3::TestOneof;
50+
using ::proto3::TestStringMapOverlay;
5051
using ::proto3::TestWrapper;
5152
using ::testing::ContainsRegex;
5253
using ::testing::ElementsAre;
@@ -645,6 +646,106 @@ TEST_P(JsonTest, ParseMap) {
645646
EXPECT_EQ(other->DebugString(), message.DebugString());
646647
}
647648

649+
TEST_P(JsonTest, BinaryToJsonStreamMapEntryWithNoValue) {
650+
TestStringMapOverlay emulated_message;
651+
auto* entry = emulated_message.add_string_map();
652+
entry->set_key("hello");
653+
654+
std::string proto_data_emulated = emulated_message.SerializeAsString();
655+
io::ArrayInputStream in(proto_data_emulated.data(),
656+
proto_data_emulated.size());
657+
658+
std::string printed;
659+
io::StringOutputStream out(&printed);
660+
661+
PrintOptions options = {};
662+
auto status = BinaryToJsonStream(resolver_.get(),
663+
"type.googleapis.com/proto3.TestStringMap",
664+
&in, &out, options);
665+
ASSERT_OK(status);
666+
667+
ASSERT_EQ(printed, R"({"stringMap":{"hello":""}})");
668+
}
669+
670+
TEST_P(JsonTest, BinaryToJsonStreamMapEntryWithNoKey) {
671+
TestStringMapOverlay emulated_message;
672+
auto* entry = emulated_message.add_string_map();
673+
entry->set_value("1234");
674+
675+
std::string proto_data_emulated = emulated_message.SerializeAsString();
676+
io::ArrayInputStream in(proto_data_emulated.data(),
677+
proto_data_emulated.size());
678+
679+
std::string printed;
680+
io::StringOutputStream out(&printed);
681+
682+
PrintOptions options = {};
683+
auto status = BinaryToJsonStream(resolver_.get(),
684+
"type.googleapis.com/proto3.TestStringMap",
685+
&in, &out, options);
686+
ASSERT_OK(status);
687+
688+
ASSERT_EQ(printed, R"({"stringMap":{"":"1234"}})");
689+
}
690+
691+
TEST_P(JsonTest, BinaryToJsonStreamMapEntryWithNoKeyOrValue) {
692+
TestStringMapOverlay emulated_message;
693+
(void)emulated_message.add_string_map();
694+
695+
std::string proto_data_emulated = emulated_message.SerializeAsString();
696+
io::ArrayInputStream in(proto_data_emulated.data(),
697+
proto_data_emulated.size());
698+
699+
std::string printed;
700+
io::StringOutputStream out(&printed);
701+
702+
PrintOptions options = {};
703+
auto status = BinaryToJsonStream(resolver_.get(),
704+
"type.googleapis.com/proto3.TestStringMap",
705+
&in, &out, options);
706+
ASSERT_OK(status);
707+
708+
ASSERT_EQ(printed, R"({"stringMap":{"":""}})");
709+
}
710+
711+
TEST_P(JsonTest, BinaryToJsonStreamDuplicateNonRepeatedField) {
712+
// The wire tag for field 6 (float_value) is (6 << 3) | 5 = 0x35.
713+
// We provide multiple values for the same non-repeated field.
714+
std::string binary_data("\x35\x00\x00\x80\x3F\x35\x00\x00\x00\x40", 10);
715+
io::ArrayInputStream in(binary_data.data(), binary_data.size());
716+
717+
std::string printed;
718+
io::StringOutputStream out(&printed);
719+
720+
PrintOptions options = {};
721+
options.always_print_fields_with_no_presence = true;
722+
auto status = BinaryToJsonStream(resolver_.get(),
723+
"type.googleapis.com/proto3.TestMessage",
724+
&in, &out, options);
725+
EXPECT_THAT(status, StatusIs(absl::StatusCode::kInvalidArgument));
726+
EXPECT_THAT(status.message(),
727+
ContainsRegex("repeated entries for singular field number 6"));
728+
}
729+
730+
TEST_P(JsonTest, BinaryToJsonStreamAlwaysPrintPrimitivesNoValueOnWire) {
731+
// Missing field should fall back to default value.
732+
std::string binary_data = ""; // Empty message
733+
io::ArrayInputStream in(binary_data.data(), binary_data.size());
734+
735+
std::string printed;
736+
io::StringOutputStream out(&printed);
737+
738+
PrintOptions options;
739+
options.always_print_fields_with_no_presence = true;
740+
auto status = BinaryToJsonStream(resolver_.get(),
741+
"type.googleapis.com/proto3.TestMessage",
742+
&in, &out, options);
743+
ASSERT_OK(status);
744+
745+
// Verify that floatValue is printed with default 0.
746+
EXPECT_THAT(printed, ContainsRegex(R"("floatValue":0)"));
747+
}
748+
648749
TEST_P(JsonTest, RepeatedMapKey) {
649750
EXPECT_THAT(ToProto<TestMap>(R"json({
650751
"string_map": {

src/google/protobuf/util/json_format_proto3.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ message TestStringMap {
112112
map<string, string> string_map = 1;
113113
}
114114

115+
// An overlay message of TestStringMap intended to make it easier to
116+
// construct some test cases of the binary wire format.
117+
message TestStringMapOverlay {
118+
message MapEntry {
119+
string key = 1;
120+
string value = 2;
121+
}
122+
repeated MapEntry string_map = 1;
123+
}
124+
115125
message TestWrapper {
116126
google.protobuf.BoolValue bool_value = 1;
117127
google.protobuf.Int32Value int32_value = 2;

0 commit comments

Comments
 (0)