|
8 | 8 | #include <cinttypes> |
9 | 9 | #include <cmath> |
10 | 10 | #include <cstdint> |
| 11 | +#include <cstring> |
11 | 12 | #include <memory> |
12 | 13 | #include <random> |
13 | 14 | #include <string> |
@@ -1103,6 +1104,88 @@ class LoudsTrieTest : public testing::Test { |
1103 | 1104 | } |
1104 | 1105 | }; |
1105 | 1106 |
|
| 1107 | +static void PatchFixed64ForTest(std::string* data, size_t offset, |
| 1108 | + uint64_t value) { |
| 1109 | + ASSERT_LE(offset + sizeof(value), data->size()); |
| 1110 | + memcpy(&(*data)[offset], &value, sizeof(value)); |
| 1111 | +} |
| 1112 | + |
| 1113 | +static uint64_t ReadFixed64ForTest(const std::string& data, size_t offset) { |
| 1114 | + assert(offset + sizeof(uint64_t) <= data.size()); |
| 1115 | + uint64_t value; |
| 1116 | + memcpy(&value, data.data() + offset, sizeof(value)); |
| 1117 | + return value; |
| 1118 | +} |
| 1119 | + |
| 1120 | +static Status SkipBitvectorForTest(const std::string& data, size_t* offset) { |
| 1121 | + if (*offset > data.size()) { |
| 1122 | + return Status::Corruption("offset past end"); |
| 1123 | + } |
| 1124 | + Bitvector bv; |
| 1125 | + size_t consumed = 0; |
| 1126 | + Status s = |
| 1127 | + bv.InitFromData(data.data() + *offset, data.size() - *offset, &consumed); |
| 1128 | + if (!s.ok()) { |
| 1129 | + return s; |
| 1130 | + } |
| 1131 | + *offset += consumed; |
| 1132 | + return Status::OK(); |
| 1133 | +} |
| 1134 | + |
| 1135 | +static Status FindChildCountOffsetForTest(const std::string& data, |
| 1136 | + size_t* child_count_offset) { |
| 1137 | + size_t offset = 56; |
| 1138 | + for (int i = 0; i < 3; i++) { |
| 1139 | + Status s = SkipBitvectorForTest(data, &offset); |
| 1140 | + if (!s.ok()) { |
| 1141 | + return s; |
| 1142 | + } |
| 1143 | + } |
| 1144 | + if (offset + sizeof(uint64_t) > data.size()) { |
| 1145 | + return Status::Corruption("truncated sparse labels size"); |
| 1146 | + } |
| 1147 | + uint64_t sparse_labels_size = ReadFixed64ForTest(data, offset); |
| 1148 | + offset += sizeof(uint64_t); |
| 1149 | + size_t sparse_labels_padded = (sparse_labels_size + 7) & ~size_t(7); |
| 1150 | + if (offset + sparse_labels_padded > data.size()) { |
| 1151 | + return Status::Corruption("truncated sparse labels"); |
| 1152 | + } |
| 1153 | + offset += sparse_labels_padded; |
| 1154 | + for (int i = 0; i < 3; i++) { |
| 1155 | + Status s = SkipBitvectorForTest(data, &offset); |
| 1156 | + if (!s.ok()) { |
| 1157 | + return s; |
| 1158 | + } |
| 1159 | + } |
| 1160 | + if (offset + sizeof(uint64_t) > data.size()) { |
| 1161 | + return Status::Corruption("truncated child count"); |
| 1162 | + } |
| 1163 | + *child_count_offset = offset; |
| 1164 | + return Status::OK(); |
| 1165 | +} |
| 1166 | + |
| 1167 | +static Status FindChainCountOffsetForTest(const std::string& data, |
| 1168 | + size_t* chain_count_offset) { |
| 1169 | + size_t offset = 0; |
| 1170 | + Status s = FindChildCountOffsetForTest(data, &offset); |
| 1171 | + if (!s.ok()) { |
| 1172 | + return s; |
| 1173 | + } |
| 1174 | + uint64_t num_internal = ReadFixed64ForTest(data, offset); |
| 1175 | + offset += sizeof(uint64_t); |
| 1176 | + size_t child_table_bytes = num_internal * sizeof(uint32_t); |
| 1177 | + size_t child_table_padded = (child_table_bytes + 7) & ~size_t(7); |
| 1178 | + if (offset + child_table_padded + child_table_padded > data.size()) { |
| 1179 | + return Status::Corruption("truncated child tables"); |
| 1180 | + } |
| 1181 | + offset += child_table_padded + child_table_padded; |
| 1182 | + if (offset + sizeof(uint64_t) > data.size()) { |
| 1183 | + return Status::Corruption("truncated chain count"); |
| 1184 | + } |
| 1185 | + *chain_count_offset = offset; |
| 1186 | + return Status::OK(); |
| 1187 | +} |
| 1188 | + |
1106 | 1189 | // Helper: build a trie from sorted keys and verify that Seek and Next |
1107 | 1190 | // iterate in exactly the expected order, checking both handles and |
1108 | 1191 | // reconstructed keys. |
@@ -1830,6 +1913,104 @@ TEST_F(LoudsTrieTest, InitFromDataMaxDepthCorruption) { |
1830 | 1913 | ASSERT_TRUE(trie3.InitFromData(Slice(header2)).IsCorruption()); |
1831 | 1914 | } |
1832 | 1915 |
|
| 1916 | +TEST_F(LoudsTrieTest, InitFromDataRejectsLeafCountMismatch) { |
| 1917 | + std::vector<std::string> keys = {"alpha", "beta", "delta", "gamma"}; |
| 1918 | + auto bt = BuildTrieFromKeys(keys); |
| 1919 | + std::string data(bt.builder.GetSerializedData().data(), |
| 1920 | + bt.builder.GetSerializedData().size()); |
| 1921 | + |
| 1922 | + PatchFixed64ForTest(&data, /*num_keys offset=*/8, keys.size() - 1); |
| 1923 | + |
| 1924 | + LoudsTrie trie; |
| 1925 | + Status s = trie.InitFromData(Slice(data)); |
| 1926 | + ASSERT_TRUE(s.IsCorruption()) << s.ToString(); |
| 1927 | +} |
| 1928 | + |
| 1929 | +TEST_F(LoudsTrieTest, InitFromDataRejectsDenseLeafCountMismatch) { |
| 1930 | + std::vector<std::string> keys = {"alpha", "beta", "delta", "gamma"}; |
| 1931 | + auto bt = BuildTrieFromKeys(keys); |
| 1932 | + std::string data(bt.builder.GetSerializedData().data(), |
| 1933 | + bt.builder.GetSerializedData().size()); |
| 1934 | + uint64_t dense_leaf_count = ReadFixed64ForTest(data, 24); |
| 1935 | + |
| 1936 | + PatchFixed64ForTest(&data, /*dense_leaf_count offset=*/24, |
| 1937 | + dense_leaf_count + 1); |
| 1938 | + |
| 1939 | + LoudsTrie trie; |
| 1940 | + Status s = trie.InitFromData(Slice(data)); |
| 1941 | + ASSERT_TRUE(s.IsCorruption()) << s.ToString(); |
| 1942 | +} |
| 1943 | + |
| 1944 | +TEST_F(LoudsTrieTest, InitFromDataRejectsSeqnoLeafCountMismatch) { |
| 1945 | + LoudsTrieBuilder builder; |
| 1946 | + builder.SetHasSeqnoEncoding(true); |
| 1947 | + std::vector<std::string> keys = {"alpha", "beta", "delta", "gamma"}; |
| 1948 | + for (size_t i = 0; i < keys.size(); i++) { |
| 1949 | + builder.AddKeyWithSeqno(Slice(keys[i]), TrieBlockHandle{i * 100, 50}, |
| 1950 | + /*seqno=*/i + 1, /*block_count=*/1); |
| 1951 | + } |
| 1952 | + builder.Finish(); |
| 1953 | + std::string data(builder.GetSerializedData().data(), |
| 1954 | + builder.GetSerializedData().size()); |
| 1955 | + |
| 1956 | + PatchFixed64ForTest(&data, /*num_keys offset=*/8, keys.size() - 1); |
| 1957 | + |
| 1958 | + LoudsTrie trie; |
| 1959 | + Status s = trie.InitFromData(Slice(data)); |
| 1960 | + ASSERT_TRUE(s.IsCorruption()) << s.ToString(); |
| 1961 | +} |
| 1962 | + |
| 1963 | +TEST_F(LoudsTrieTest, InitFromDataRejectsSparseChildCountMismatch) { |
| 1964 | + std::vector<std::string> keys; |
| 1965 | + for (int i = 0; i < 20; i++) { |
| 1966 | + std::string key(1, static_cast<char>('A' + i)); |
| 1967 | + key += 'x'; |
| 1968 | + keys.push_back(key); |
| 1969 | + } |
| 1970 | + auto bt = BuildTrieFromKeys(keys); |
| 1971 | + std::string data(bt.builder.GetSerializedData().data(), |
| 1972 | + bt.builder.GetSerializedData().size()); |
| 1973 | + size_t child_count_offset = 0; |
| 1974 | + ASSERT_OK(FindChildCountOffsetForTest(data, &child_count_offset)); |
| 1975 | + uint64_t num_internal = ReadFixed64ForTest(data, child_count_offset); |
| 1976 | + ASSERT_GT(num_internal, 0u); |
| 1977 | + |
| 1978 | + PatchFixed64ForTest(&data, child_count_offset, num_internal - 1); |
| 1979 | + |
| 1980 | + LoudsTrie trie; |
| 1981 | + Status s = trie.InitFromData(Slice(data)); |
| 1982 | + ASSERT_TRUE(s.IsCorruption()) << s.ToString(); |
| 1983 | +} |
| 1984 | + |
| 1985 | +TEST_F(LoudsTrieTest, InitFromDataRejectsChainBitmapCountMismatch) { |
| 1986 | + std::vector<std::string> keys; |
| 1987 | + std::string prefix(10, 'A'); |
| 1988 | + for (int i = 0; i < 20; i++) { |
| 1989 | + char suffix[8]; |
| 1990 | + snprintf(suffix, sizeof(suffix), "%02d", i); |
| 1991 | + keys.push_back(prefix + suffix); |
| 1992 | + } |
| 1993 | + std::sort(keys.begin(), keys.end()); |
| 1994 | + auto bt = BuildTrieFromKeys(keys); |
| 1995 | + ASSERT_TRUE(bt.trie.HasChains()); |
| 1996 | + std::string data(bt.builder.GetSerializedData().data(), |
| 1997 | + bt.builder.GetSerializedData().size()); |
| 1998 | + size_t child_count_offset = 0; |
| 1999 | + ASSERT_OK(FindChildCountOffsetForTest(data, &child_count_offset)); |
| 2000 | + uint64_t num_internal = ReadFixed64ForTest(data, child_count_offset); |
| 2001 | + size_t chain_count_offset = 0; |
| 2002 | + ASSERT_OK(FindChainCountOffsetForTest(data, &chain_count_offset)); |
| 2003 | + uint64_t num_chains = ReadFixed64ForTest(data, chain_count_offset); |
| 2004 | + ASSERT_GT(num_chains, 0u); |
| 2005 | + ASSERT_LT(num_chains, num_internal); |
| 2006 | + |
| 2007 | + PatchFixed64ForTest(&data, chain_count_offset, num_chains + 1); |
| 2008 | + |
| 2009 | + LoudsTrie trie; |
| 2010 | + Status s = trie.InitFromData(Slice(data)); |
| 2011 | + ASSERT_TRUE(s.IsCorruption()) << s.ToString(); |
| 2012 | +} |
| 2013 | + |
1833 | 2014 | TEST_F(LoudsTrieTest, MoveConstructor) { |
1834 | 2015 | // Verify that move constructor works correctly for LoudsTrie, which |
1835 | 2016 | // contains Bitvector members with RecomputePointers() logic. |
|
0 commit comments