Skip to content

Commit bf1ae22

Browse files
Update proto parsing to keep track of parsed fields that have no presence and were parsed to their default value, hence had no effect on the result proto.
Update the Partially proto matcher to cause proto parsing to check fail if the above condition was triggered. This is to address a known issue where test are silently passing when proto3 protos are the arguments to Partially(EqualsProto) and have some of the fields ignored by the EqualsProto matcher since they were set to their default value in the expected proto. PiperOrigin-RevId: 518262030
1 parent 35ffb66 commit bf1ae22

File tree

3 files changed

+134
-27
lines changed

3 files changed

+134
-27
lines changed

src/google/protobuf/text_format.cc

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,8 @@ class TextFormat::Parser::ParserImpl {
320320
bool allow_case_insensitive_field, bool allow_unknown_field,
321321
bool allow_unknown_extension, bool allow_unknown_enum,
322322
bool allow_field_number, bool allow_relaxed_whitespace,
323-
bool allow_partial, int recursion_limit)
323+
bool allow_partial, int recursion_limit,
324+
bool error_on_no_op_fields)
324325
: error_collector_(error_collector),
325326
finder_(finder),
326327
parse_info_tree_(parse_info_tree),
@@ -337,7 +338,8 @@ class TextFormat::Parser::ParserImpl {
337338
initial_recursion_limit_(recursion_limit),
338339
recursion_limit_(recursion_limit),
339340
had_silent_marker_(false),
340-
had_errors_(false) {
341+
had_errors_(false),
342+
error_on_no_op_fields_(error_on_no_op_fields) {
341343
// For backwards-compatibility with proto1, we need to allow the 'f' suffix
342344
// for floats.
343345
tokenizer_.set_allow_f_after_float(true);
@@ -817,75 +819,86 @@ class TextFormat::Parser::ParserImpl {
817819
// Define an easy to use macro for setting fields. This macro checks
818820
// to see if the field is repeated (in which case we need to use the Add
819821
// methods or not (in which case we need to use the Set methods).
820-
#define SET_FIELD(CPPTYPE, VALUE) \
821-
if (field->is_repeated()) { \
822-
reflection->Add##CPPTYPE(message, field, VALUE); \
823-
} else { \
824-
reflection->Set##CPPTYPE(message, field, VALUE); \
822+
// When checking for no-op operations, We verify that both the existing value in
823+
// the message and the new value are the default. If the existing field value is
824+
// not the default, setting it to the default should not be treated as a no-op.
825+
#define SET_FIELD(CPPTYPE, CPPTYPELCASE, VALUE) \
826+
if (field->is_repeated()) { \
827+
reflection->Add##CPPTYPE(message, field, VALUE); \
828+
} else { \
829+
if (error_on_no_op_fields_ && !field->has_presence() && \
830+
field->default_value_##CPPTYPELCASE() == \
831+
reflection->Get##CPPTYPE(*message, field) && \
832+
field->default_value_##CPPTYPELCASE() == VALUE) { \
833+
ReportError("Input field " + field->full_name() + \
834+
" did not change resulting proto."); \
835+
} else { \
836+
reflection->Set##CPPTYPE(message, field, std::move(VALUE)); \
837+
} \
825838
}
826839

827840
switch (field->cpp_type()) {
828841
case FieldDescriptor::CPPTYPE_INT32: {
829842
int64_t value;
830843
DO(ConsumeSignedInteger(&value, kint32max));
831-
SET_FIELD(Int32, static_cast<int32_t>(value));
844+
SET_FIELD(Int32, int32, static_cast<int32_t>(value));
832845
break;
833846
}
834847

835848
case FieldDescriptor::CPPTYPE_UINT32: {
836849
uint64_t value;
837850
DO(ConsumeUnsignedInteger(&value, kuint32max));
838-
SET_FIELD(UInt32, static_cast<uint32_t>(value));
851+
SET_FIELD(UInt32, uint32, static_cast<uint32_t>(value));
839852
break;
840853
}
841854

842855
case FieldDescriptor::CPPTYPE_INT64: {
843856
int64_t value;
844857
DO(ConsumeSignedInteger(&value, kint64max));
845-
SET_FIELD(Int64, value);
858+
SET_FIELD(Int64, int64, value);
846859
break;
847860
}
848861

849862
case FieldDescriptor::CPPTYPE_UINT64: {
850863
uint64_t value;
851864
DO(ConsumeUnsignedInteger(&value, kuint64max));
852-
SET_FIELD(UInt64, value);
865+
SET_FIELD(UInt64, uint64, value);
853866
break;
854867
}
855868

856869
case FieldDescriptor::CPPTYPE_FLOAT: {
857870
double value;
858871
DO(ConsumeDouble(&value));
859-
SET_FIELD(Float, io::SafeDoubleToFloat(value));
872+
SET_FIELD(Float, float, io::SafeDoubleToFloat(value));
860873
break;
861874
}
862875

863876
case FieldDescriptor::CPPTYPE_DOUBLE: {
864877
double value;
865878
DO(ConsumeDouble(&value));
866-
SET_FIELD(Double, value);
879+
SET_FIELD(Double, double, value);
867880
break;
868881
}
869882

870883
case FieldDescriptor::CPPTYPE_STRING: {
871884
std::string value;
872885
DO(ConsumeString(&value));
873-
SET_FIELD(String, std::move(value));
886+
SET_FIELD(String, string, value);
874887
break;
875888
}
876889

877890
case FieldDescriptor::CPPTYPE_BOOL: {
878891
if (LookingAtType(io::Tokenizer::TYPE_INTEGER)) {
879892
uint64_t value;
880893
DO(ConsumeUnsignedInteger(&value, 1));
881-
SET_FIELD(Bool, value);
894+
SET_FIELD(Bool, bool, value);
882895
} else {
883896
std::string value;
884897
DO(ConsumeIdentifier(&value));
885898
if (value == "true" || value == "True" || value == "t") {
886-
SET_FIELD(Bool, true);
899+
SET_FIELD(Bool, bool, true);
887900
} else if (value == "false" || value == "False" || value == "f") {
888-
SET_FIELD(Bool, false);
901+
SET_FIELD(Bool, bool, false);
889902
} else {
890903
ReportError(absl::StrCat("Invalid value for boolean field \"",
891904
field->name(), "\". Value: \"", value,
@@ -921,7 +934,7 @@ class TextFormat::Parser::ParserImpl {
921934
if (enum_value == nullptr) {
922935
if (int_value != kint64max &&
923936
!field->legacy_enum_field_treated_as_closed()) {
924-
SET_FIELD(EnumValue, int_value);
937+
SET_FIELD(EnumValue, int64, int_value);
925938
return true;
926939
} else if (!allow_unknown_enum_) {
927940
ReportError(absl::StrCat("Unknown enumeration value of \"", value,
@@ -935,7 +948,7 @@ class TextFormat::Parser::ParserImpl {
935948
}
936949
}
937950

938-
SET_FIELD(Enum, enum_value);
951+
SET_FIELD(Enum, enum, enum_value);
939952
break;
940953
}
941954

@@ -1404,6 +1417,8 @@ class TextFormat::Parser::ParserImpl {
14041417
int recursion_limit_;
14051418
bool had_silent_marker_;
14061419
bool had_errors_;
1420+
bool error_on_no_op_fields_;
1421+
14071422
};
14081423

14091424
// ===========================================================================
@@ -1700,7 +1715,7 @@ bool TextFormat::Parser::Parse(io::ZeroCopyInputStream* input,
17001715
allow_case_insensitive_field_, allow_unknown_field_,
17011716
allow_unknown_extension_, allow_unknown_enum_,
17021717
allow_field_number_, allow_relaxed_whitespace_,
1703-
allow_partial_, recursion_limit_);
1718+
allow_partial_, recursion_limit_, error_on_no_op_fields_);
17041719
return MergeUsingImpl(input, output, &parser);
17051720
}
17061721

@@ -1725,7 +1740,7 @@ bool TextFormat::Parser::Merge(io::ZeroCopyInputStream* input,
17251740
allow_case_insensitive_field_, allow_unknown_field_,
17261741
allow_unknown_extension_, allow_unknown_enum_,
17271742
allow_field_number_, allow_relaxed_whitespace_,
1728-
allow_partial_, recursion_limit_);
1743+
allow_partial_, recursion_limit_, error_on_no_op_fields_);
17291744
return MergeUsingImpl(input, output, &parser);
17301745
}
17311746

@@ -1755,12 +1770,13 @@ bool TextFormat::Parser::ParseFieldValueFromString(const std::string& input,
17551770
const FieldDescriptor* field,
17561771
Message* output) {
17571772
io::ArrayInputStream input_stream(input.data(), input.size());
1758-
ParserImpl parser(
1759-
output->GetDescriptor(), &input_stream, error_collector_, finder_,
1760-
parse_info_tree_, ParserImpl::ALLOW_SINGULAR_OVERWRITES,
1761-
allow_case_insensitive_field_, allow_unknown_field_,
1762-
allow_unknown_extension_, allow_unknown_enum_, allow_field_number_,
1763-
allow_relaxed_whitespace_, allow_partial_, recursion_limit_);
1773+
ParserImpl parser(output->GetDescriptor(), &input_stream, error_collector_,
1774+
finder_, parse_info_tree_,
1775+
ParserImpl::ALLOW_SINGULAR_OVERWRITES,
1776+
allow_case_insensitive_field_, allow_unknown_field_,
1777+
allow_unknown_extension_, allow_unknown_enum_,
1778+
allow_field_number_, allow_relaxed_whitespace_,
1779+
allow_partial_, recursion_limit_, error_on_no_op_fields_);
17641780
return parser.ParseField(field, output);
17651781
}
17661782

src/google/protobuf/text_format.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,13 @@ class PROTOBUF_EXPORT TextFormat {
694694
// the maximum allowed nesting of proto messages.
695695
void SetRecursionLimit(int limit) { recursion_limit_ = limit; }
696696

697+
// If called, the parser will report an error if a parsed field had no
698+
// effect on the resulting proto (for example, fields with no presence that
699+
// were set to their default value).
700+
void ErrorOnNoOpFields(bool return_error) {
701+
error_on_no_op_fields_ = return_error;
702+
}
703+
697704
private:
698705
// Forward declaration of an internal class used to parse text
699706
// representations (see text_format.cc for implementation).
@@ -716,6 +723,7 @@ class PROTOBUF_EXPORT TextFormat {
716723
bool allow_relaxed_whitespace_;
717724
bool allow_singular_overwrites_;
718725
int recursion_limit_;
726+
bool error_on_no_op_fields_ = false;
719727
};
720728

721729

src/google/protobuf/text_format_unittest.cc

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,89 @@ TEST_F(TextFormatTest, ParseUnknownEnumFieldProto3) {
901901
EXPECT_EQ(-2147483648, proto.repeated_nested_enum(3));
902902
}
903903

904+
TEST_F(TextFormatTest, ErrorOnNoOpFieldsProto3) {
905+
proto3_unittest::TestAllTypes proto;
906+
TextFormat::Parser parser;
907+
parser.ErrorOnNoOpFields(true);
908+
909+
{
910+
const absl::string_view singular_int_parse_string = "optional_int32: 0";
911+
EXPECT_TRUE(TextFormat::ParseFromString(singular_int_parse_string, &proto));
912+
EXPECT_FALSE(parser.ParseFromString(singular_int_parse_string, &proto));
913+
}
914+
{
915+
const absl::string_view singular_bool_parse_string = "optional_bool: false";
916+
EXPECT_TRUE(
917+
TextFormat::ParseFromString(singular_bool_parse_string, &proto));
918+
EXPECT_FALSE(parser.ParseFromString(singular_bool_parse_string, &proto));
919+
}
920+
{
921+
const absl::string_view singular_string_parse_string =
922+
"optional_string: ''";
923+
EXPECT_TRUE(
924+
TextFormat::ParseFromString(singular_string_parse_string, &proto));
925+
EXPECT_FALSE(parser.ParseFromString(singular_string_parse_string, &proto));
926+
}
927+
{
928+
const absl::string_view nested_message_parse_string =
929+
"optional_nested_message { bb: 0 } ";
930+
EXPECT_TRUE(
931+
TextFormat::ParseFromString(nested_message_parse_string, &proto));
932+
EXPECT_FALSE(parser.ParseFromString(nested_message_parse_string, &proto));
933+
}
934+
{
935+
const absl::string_view foreign_message_parse_string =
936+
"optional_foreign_message { c: 0 } ";
937+
EXPECT_TRUE(
938+
TextFormat::ParseFromString(foreign_message_parse_string, &proto));
939+
EXPECT_FALSE(parser.ParseFromString(foreign_message_parse_string, &proto));
940+
}
941+
{
942+
const absl::string_view nested_enum_parse_string =
943+
"optional_nested_enum: ZERO ";
944+
EXPECT_TRUE(TextFormat::ParseFromString(nested_enum_parse_string, &proto));
945+
EXPECT_FALSE(parser.ParseFromString(nested_enum_parse_string, &proto));
946+
}
947+
{
948+
const absl::string_view foreign_enum_parse_string =
949+
"optional_foreign_enum: FOREIGN_ZERO ";
950+
EXPECT_TRUE(TextFormat::ParseFromString(foreign_enum_parse_string, &proto));
951+
EXPECT_FALSE(parser.ParseFromString(foreign_enum_parse_string, &proto));
952+
}
953+
{
954+
const absl::string_view string_piece_parse_string =
955+
"optional_string_piece: '' ";
956+
EXPECT_TRUE(TextFormat::ParseFromString(string_piece_parse_string, &proto));
957+
EXPECT_FALSE(parser.ParseFromString(string_piece_parse_string, &proto));
958+
}
959+
{
960+
const absl::string_view cord_parse_string = "optional_cord: '' ";
961+
EXPECT_TRUE(TextFormat::ParseFromString(cord_parse_string, &proto));
962+
EXPECT_FALSE(parser.ParseFromString(cord_parse_string, &proto));
963+
}
964+
{
965+
// Sanity check that repeated fields work the same.
966+
const absl::string_view repeated_int32_parse_string = "repeated_int32: 0 ";
967+
EXPECT_TRUE(
968+
TextFormat::ParseFromString(repeated_int32_parse_string, &proto));
969+
EXPECT_TRUE(parser.ParseFromString(repeated_int32_parse_string, &proto));
970+
}
971+
{
972+
const absl::string_view repeated_bool_parse_string =
973+
"repeated_bool: false ";
974+
EXPECT_TRUE(
975+
TextFormat::ParseFromString(repeated_bool_parse_string, &proto));
976+
EXPECT_TRUE(parser.ParseFromString(repeated_bool_parse_string, &proto));
977+
}
978+
{
979+
const absl::string_view repeated_string_parse_string =
980+
"repeated_string: '' ";
981+
EXPECT_TRUE(
982+
TextFormat::ParseFromString(repeated_string_parse_string, &proto));
983+
EXPECT_TRUE(parser.ParseFromString(repeated_string_parse_string, &proto));
984+
}
985+
}
986+
904987
TEST_F(TextFormatTest, ParseStringEscape) {
905988
// Create a parse string with escaped characters in it.
906989
std::string parse_string =

0 commit comments

Comments
 (0)