Skip to content

Commit 594a71f

Browse files
Add an optimization that prevents an exponential number of comparisons on deeply
nested repeated fields when using AS_SET or AS_SMART_SET. PiperOrigin-RevId: 645123131
1 parent aca4cfc commit 594a71f

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

src/google/protobuf/util/message_differencer.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,6 +1913,29 @@ bool MessageDifferencer::MatchRepeatedFieldIndices(
19131913

19141914
match_list1->assign(count1, -1);
19151915
match_list2->assign(count2, -1);
1916+
1917+
// In the special case where both repeated fields have exactly one element,
1918+
// return without calling the comparator. This optimization prevents the
1919+
// pathological case of deeply nested repeated fields of size 1 from taking
1920+
// exponential-time to compare.
1921+
//
1922+
// In the case where reporter_ is set, we need to do the compare here to
1923+
// properly distinguish a modify from an add+delete. The code below will not
1924+
// pass the reporter along in recursive calls to nested repeated fields, so
1925+
// the inner call will have the opportunity to perform this optimization and
1926+
// avoid exponential-time behavior.
1927+
//
1928+
// In the case where key_comparator is set, we need to do the compare here to
1929+
// fulfill the interface contract that keys will be compared even if the user
1930+
// asked to ignore that field. The code will only compare the key fields
1931+
// which (hopefully) do not contain further repeated fields.
1932+
if (count1 == 1 && count2 == 1 && reporter_ == nullptr &&
1933+
key_comparator == nullptr) {
1934+
match_list1->at(0) = 0;
1935+
match_list2->at(0) = 0;
1936+
return true;
1937+
}
1938+
19161939
// Ensure that we don't report differences during the matching process. Since
19171940
// field comparators could potentially use this message differencer object to
19181941
// perform further comparisons, turn off reporting here and re-enable it

src/google/protobuf/util/message_differencer_unittest.cc

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,6 +1912,91 @@ TEST(MessageDifferencerTest, RepeatedFieldSetTest_Combination) {
19121912
EXPECT_TRUE(differencer2.Compare(msg1, msg2));
19131913
}
19141914

1915+
// This class is a comparator that uses the default comparator, but counts how
1916+
// many times it was called.
1917+
class CountingComparator : public util::SimpleFieldComparator {
1918+
public:
1919+
ComparisonResult Compare(const Message& message_1, const Message& message_2,
1920+
const FieldDescriptor* field, int index_1,
1921+
int index_2,
1922+
const util::FieldContext* field_context) override {
1923+
++compare_count_;
1924+
return SimpleCompare(message_1, message_2, field, index_1, index_2,
1925+
field_context);
1926+
}
1927+
1928+
int compare_count() const { return compare_count_; }
1929+
1930+
private:
1931+
int compare_count_ = 0;
1932+
};
1933+
1934+
TEST(MessageDifferencerTest, RepeatedFieldSet_RecursivePerformance) {
1935+
constexpr int kDepth = 20;
1936+
1937+
protobuf_unittest::TestField left;
1938+
protobuf_unittest::TestField* p = &left;
1939+
for (int i = 0; i < kDepth; ++i) {
1940+
p = p->add_rm();
1941+
}
1942+
1943+
protobuf_unittest::TestField right = left;
1944+
util::MessageDifferencer differencer;
1945+
differencer.set_repeated_field_comparison(
1946+
util::MessageDifferencer::RepeatedFieldComparison::AS_SET);
1947+
CountingComparator comparator;
1948+
differencer.set_field_comparator(&comparator);
1949+
std::string report;
1950+
differencer.ReportDifferencesToString(&report);
1951+
differencer.Compare(left, right);
1952+
1953+
EXPECT_LE(comparator.compare_count(), kDepth * kDepth);
1954+
}
1955+
1956+
TEST(MessageDifferencerTest, RepeatedFieldSmartSet_RecursivePerformance) {
1957+
constexpr int kDepth = 20;
1958+
1959+
protobuf_unittest::TestField left;
1960+
protobuf_unittest::TestField* p = &left;
1961+
for (int i = 0; i < kDepth; ++i) {
1962+
p = p->add_rm();
1963+
}
1964+
1965+
protobuf_unittest::TestField right = left;
1966+
util::MessageDifferencer differencer;
1967+
differencer.set_repeated_field_comparison(
1968+
util::MessageDifferencer::RepeatedFieldComparison::AS_SMART_SET);
1969+
CountingComparator comparator;
1970+
differencer.set_field_comparator(&comparator);
1971+
std::string report;
1972+
differencer.ReportDifferencesToString(&report);
1973+
differencer.Compare(left, right);
1974+
1975+
EXPECT_LE(comparator.compare_count(), kDepth * kDepth);
1976+
}
1977+
1978+
TEST(MessageDifferencerTest, RepeatedFieldSmartList_RecursivePerformance) {
1979+
constexpr int kDepth = 20;
1980+
1981+
protobuf_unittest::TestField left;
1982+
protobuf_unittest::TestField* p = &left;
1983+
for (int i = 0; i < kDepth; ++i) {
1984+
p = p->add_rm();
1985+
}
1986+
1987+
protobuf_unittest::TestField right = left;
1988+
util::MessageDifferencer differencer;
1989+
differencer.set_repeated_field_comparison(
1990+
util::MessageDifferencer::RepeatedFieldComparison::AS_SMART_LIST);
1991+
CountingComparator comparator;
1992+
differencer.set_field_comparator(&comparator);
1993+
std::string report;
1994+
differencer.ReportDifferencesToString(&report);
1995+
differencer.Compare(left, right);
1996+
1997+
EXPECT_LE(comparator.compare_count(), kDepth * kDepth);
1998+
}
1999+
19152000
TEST(MessageDifferencerTest, RepeatedFieldMapTest_Partial) {
19162001
protobuf_unittest::TestDiffMessage msg1;
19172002
// message msg1 {

src/google/protobuf/util/message_differencer_unittest.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ message TestField {
2626
optional int32 c = 1;
2727
repeated int32 rc = 2;
2828
optional TestField m = 5;
29+
repeated TestField rm = 6;
2930

3031
extend TestDiffMessage {
3132
optional TestField tf = 100;

0 commit comments

Comments
 (0)