From d86fb9f87a3be1bbeef6aecfb109e48d139413dc Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 18 Jan 2019 20:54:22 -0600 Subject: [PATCH] GH-151: fix hash calculation for nested sets Hash key construction of nested sets depended on the order in which their elements are iterated, which varied even between sets containing equivalent elements. The iteration order is now sorted by each element's hash value (or, on collision, by full key) such that equivalent sets no longer hash differently. --- src/CompHash.cc | 41 +++++++++++++++++-- .../output | 14 +++---- testing/btest/language/nested-sets.bro | 19 +++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 testing/btest/language/nested-sets.bro diff --git a/src/CompHash.cc b/src/CompHash.cc index f120c3618b..81ff353ab8 100644 --- a/src/CompHash.cc +++ b/src/CompHash.cc @@ -7,6 +7,9 @@ #include "Reporter.h" #include "Func.h" +#include +#include + CompositeHash::CompositeHash(TypeList* composite_type) { type = composite_type; @@ -174,12 +177,44 @@ char* CompositeHash::SingleValHash(int type_check, char* kp0, { int* kp = AlignAndPadType(kp0); TableVal* tv = v->AsTableVal(); - ListVal* lv = tv->ConvertToList(); *kp = tv->Size(); kp1 = reinterpret_cast(kp+1); - for ( int i = 0; i < tv->Size(); ++i ) + + auto tbl = tv->AsTable(); + auto it = tbl->InitForIteration(); + ListVal* lv = new ListVal(TYPE_ANY); + + struct HashKeyComparer { + bool operator()(const HashKey* a, const HashKey* b) + { + if ( a->Hash() != b->Hash() ) + return a->Hash() < b->Hash(); + if ( a->Size() != b->Size() ) + return a->Size() < b->Size(); + return strncmp(static_cast(a->Key()), + static_cast(b->Key()), + a->Size()) < 0; + } + }; + + std::map hashkeys; + HashKey* k; + auto idx = 0; + + while ( tbl->NextEntry(k, it) ) { - Val* key = lv->Index(i); + hashkeys[k] = idx++; + lv->Append(tv->RecoverIndex(k)); + } + + for ( auto& kv : hashkeys ) + delete kv.first; + + for ( auto& kv : hashkeys ) + { + auto idx = kv.second; + Val* key = lv->Index(idx); + if ( ! (kp1 = SingleValHash(type_check, kp1, key->Type(), key, false)) ) { diff --git a/testing/btest/Baseline/language.record-index-complex-fields/output b/testing/btest/Baseline/language.record-index-complex-fields/output index f47493ea82..c07d86ee5e 100644 --- a/testing/btest/Baseline/language.record-index-complex-fields/output +++ b/testing/btest/Baseline/language.record-index-complex-fields/output @@ -10,18 +10,18 @@ a } } { -[a=4, tags_v=[0, 1], tags_t={ -[two] = 2, -[one] = 1 -}, tags_s={ -b, -a -}], [a=13, tags_v=[, , 2, 3], tags_t={ [five] = 5, [four] = 4 }, tags_s={ c, d +}], +[a=4, tags_v=[0, 1], tags_t={ +[two] = 2, +[one] = 1 +}, tags_s={ +b, +a }] } diff --git a/testing/btest/language/nested-sets.bro b/testing/btest/language/nested-sets.bro new file mode 100644 index 0000000000..e33e1ac842 --- /dev/null +++ b/testing/btest/language/nested-sets.bro @@ -0,0 +1,19 @@ +# @TEST-EXEC: for i in `seq 21`; do echo 0 >> random.seed; done +# @TEST-EXEC: test `bro -b -G random.seed %INPUT` = "pass" + +type r: record { + b: set[count]; +}; + +global foo: set[r]; +global bar = set(1,3,5); + +add foo[record($b=bar)]; + +bar = set(5,3,1); +delete foo[record($b=bar)]; + +if ( |foo| > 0 ) + print "fail"; +else + print "pass";