diff options
Diffstat (limited to 'absl/strings')
-rw-r--r-- | absl/strings/cord_test.cc | 41 | ||||
-rw-r--r-- | absl/strings/internal/cord_rep_btree_reader.cc | 6 | ||||
-rw-r--r-- | absl/strings/internal/cord_rep_btree_reader.h | 58 | ||||
-rw-r--r-- | absl/strings/internal/cord_rep_btree_reader_test.cc | 62 |
4 files changed, 101 insertions, 66 deletions
diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 06a7bd6c..50079b7c 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -1591,7 +1591,7 @@ TEST_P(CordTest, CordChunkIteratorOperations) { VerifyChunkIterator(subcords, 128); } -TEST(CordCharIterator, Traits) { +TEST_P(CordTest, CharIteratorTraits) { static_assert(std::is_copy_constructible<absl::Cord::CharIterator>::value, ""); static_assert(std::is_copy_assignable<absl::Cord::CharIterator>::value, ""); @@ -1700,7 +1700,7 @@ static void VerifyCharIterator(const absl::Cord& cord) { } } -TEST(CordCharIterator, Operations) { +TEST_P(CordTest, CharIteratorOperations) { absl::Cord empty_cord; VerifyCharIterator(empty_cord); @@ -1729,6 +1729,41 @@ TEST(CordCharIterator, Operations) { VerifyCharIterator(subcords); } +TEST_P(CordTest, CharIteratorAdvanceAndRead) { + // Create a Cord holding 6 flats of 2500 bytes each, and then iterate over it + // reading 150, 1500, 2500 and 3000 bytes. This will result in all possible + // partial, full and straddled read combinations including reads below + // kMaxBytesToCopy. b/197776822 surfaced a bug for a specific partial, small + // read 'at end' on Cord which caused a failure on attempting to read past the + // end in CordRepBtreeReader which was not covered by any existing test. + constexpr int kBlocks = 6; + constexpr size_t kBlockSize = 2500; + constexpr size_t kChunkSize1 = 1500; + constexpr size_t kChunkSize2 = 2500; + constexpr size_t kChunkSize3 = 3000; + constexpr size_t kChunkSize4 = 150; + RandomEngine rng; + std::string data = RandomLowercaseString(&rng, kBlocks * kBlockSize); + absl::Cord cord; + for (int i = 0; i < kBlocks; ++i) { + const std::string block = data.substr(i * kBlockSize, kBlockSize); + cord.Append(absl::Cord(block)); + } + + for (size_t chunk_size : + {kChunkSize1, kChunkSize2, kChunkSize3, kChunkSize4}) { + absl::Cord::CharIterator it = cord.char_begin(); + size_t offset = 0; + while (offset < data.length()) { + const size_t n = std::min<size_t>(data.length() - offset, chunk_size); + absl::Cord chunk = cord.AdvanceAndRead(&it, n); + ASSERT_EQ(chunk.size(), n); + ASSERT_EQ(chunk.Compare(data.substr(offset, n)), 0); + offset += n; + } + } +} + TEST_P(CordTest, StreamingOutput) { absl::Cord c = absl::MakeFragmentedCord({"A ", "small ", "fragmented ", "Cord", "."}); @@ -1778,7 +1813,7 @@ TEST_P(CordTest, Format) { EXPECT_EQ(c, "There were 0003 little pigs.And 1 bad wolf!"); } -TEST(CordDeathTest, Hardening) { +TEST_P(CordTest, Hardening) { absl::Cord cord("hello"); // These statement should abort the program in all builds modes. EXPECT_DEATH_IF_SUPPORTED(cord.RemovePrefix(6), ""); diff --git a/absl/strings/internal/cord_rep_btree_reader.cc b/absl/strings/internal/cord_rep_btree_reader.cc index 3ba43144..5dc76966 100644 --- a/absl/strings/internal/cord_rep_btree_reader.cc +++ b/absl/strings/internal/cord_rep_btree_reader.cc @@ -52,14 +52,14 @@ absl::string_view CordRepBtreeReader::Read(size_t n, size_t chunk_size, // data, calling `navigator_.Current()` is not safe before checking if we // already consumed all remaining data. const size_t consumed_by_read = n - chunk_size - result.n; - if (consumed_ + consumed_by_read >= length()) { - consumed_ = length(); + if (consumed_by_read >= remaining_) { + remaining_ = 0; return {}; } // We did not read all data, return remaining data from current edge. edge = navigator_.Current(); - consumed_ += consumed_by_read + edge->length; + remaining_ -= consumed_by_read + edge->length; return CordRepBtree::EdgeData(edge).substr(result.n); } diff --git a/absl/strings/internal/cord_rep_btree_reader.h b/absl/strings/internal/cord_rep_btree_reader.h index c19fa43d..66e97f5d 100644 --- a/absl/strings/internal/cord_rep_btree_reader.h +++ b/absl/strings/internal/cord_rep_btree_reader.h @@ -31,9 +31,7 @@ namespace cord_internal { // References to the underlying data are returned as absl::string_view values. // The most typical use case is a forward only iteration over tree data. // The class also provides `Skip()`, `Seek()` and `Read()` methods similar to -// CordRepBtreeNavigator that allow more advanced navigation. The class provides -// a `consumed` property which contains the end offset of the chunk last -// returned to the user which is useful in cord iteration logic. +// CordRepBtreeNavigator that allow more advanced navigation. // // Example: iterate over all data inside a cord btree: // @@ -61,9 +59,9 @@ namespace cord_internal { // absl::string_view sv = reader.Next(); // } // -// It is important to notice that `consumed` represents the end position of the -// last data edge returned to the caller, not the cumulative data returned to -// the caller which can be less in cases of skipping or seeking over data. +// It is important to notice that `remaining` is based on the end position of +// the last data edge returned to the caller, not the cumulative data returned +// to the caller which can be less in cases of skipping or seeking over data. // // For example, consider a cord btree with five data edges: "abc", "def", "ghi", // "jkl" and "mno": @@ -71,14 +69,12 @@ namespace cord_internal { // absl::string_view sv; // CordRepBtreeReader reader; // -// sv = reader.Init(tree); // sv = "abc", reader.consumed() = 3 -// sv = reader.Skip(4); // sv = "hi", reader.consumed() = 9 -// sv = reader.Skip(2); // sv = "l", reader.consumed() = 12 -// sv = reader.Next(); // sv = "mno", reader.consumed() = 15 +// sv = reader.Init(tree); // sv = "abc", remaining = 12 +// sv = reader.Skip(4); // sv = "hi", remaining = 6 +// sv = reader.Skip(2); // sv = "l", remaining = 3 +// sv = reader.Next(); // sv = "mno", remaining = 0 +// sv = reader.Seek(1); // sv = "bc", remaining = 12 // -// In the above example, `reader.consumed()` reflects the data edges iterated -// over or skipped by the reader, not the amount of data 'consumed' by the -// caller. class CordRepBtreeReader { public: using ReadResult = CordRepBtreeNavigator::ReadResult; @@ -98,13 +94,14 @@ class CordRepBtreeReader { // Requires that the current instance is not empty. size_t length() const; - // Returns the end offset of the last navigated to chunk, which represents the - // total bytes 'consumed' relative to the start of the tree. The returned - // value is never zero. For example, initializing a reader with a tree with a - // first data edge of 19 bytes will return `consumed() = 19`. See also the - // class comments on the meaning of `consumed`. - // Requires that the current instance is not empty. - size_t consumed() const; + // Returns the number of remaining bytes available for iteration, which is the + // number of bytes directly following the end of the last chunk returned. + // This value will be zero if we iterated over the last edge in the bound + // tree, in which case any call to Next() or Skip() will return an empty + // string_view reflecting the EOF state. + // Note that a call to `Seek()` resets `remaining` to a value based on the + // end position of the chunk returned by that call. + size_t remaining() const { return remaining_; } // Resets this instance to an empty value. void Reset() { navigator_.Reset(); } @@ -157,7 +154,7 @@ class CordRepBtreeReader { absl::string_view Seek(size_t offset); private: - size_t consumed_; + size_t remaining_ = 0; CordRepBtreeNavigator navigator_; }; @@ -166,23 +163,18 @@ inline size_t CordRepBtreeReader::length() const { return btree()->length; } -inline size_t CordRepBtreeReader::consumed() const { - assert(btree() != nullptr); - return consumed_; -} - inline absl::string_view CordRepBtreeReader::Init(CordRepBtree* tree) { assert(tree != nullptr); const CordRep* edge = navigator_.InitFirst(tree); - consumed_ = edge->length; + remaining_ = tree->length - edge->length;; return CordRepBtree::EdgeData(edge); } inline absl::string_view CordRepBtreeReader::Next() { - assert(consumed() < length()); + if (remaining_ == 0) return {}; const CordRep* edge = navigator_.Next(); assert(edge != nullptr); - consumed_ += edge->length; + remaining_ -= edge->length; return CordRepBtree::EdgeData(edge); } @@ -192,23 +184,23 @@ inline absl::string_view CordRepBtreeReader::Skip(size_t skip) { const size_t edge_length = navigator_.Current()->length; CordRepBtreeNavigator::Position pos = navigator_.Skip(skip + edge_length); if (ABSL_PREDICT_FALSE(pos.edge == nullptr)) { - consumed_ = length(); + remaining_ = 0; return {}; } // The combined length of all edges skipped before `pos.edge` is `skip - // pos.offset`, all of which are 'consumed', as well as the current edge. - consumed_ += skip - pos.offset + pos.edge->length; + remaining_ -= skip - pos.offset + pos.edge->length; return CordRepBtree::EdgeData(pos.edge).substr(pos.offset); } inline absl::string_view CordRepBtreeReader::Seek(size_t offset) { const CordRepBtreeNavigator::Position pos = navigator_.Seek(offset); if (ABSL_PREDICT_FALSE(pos.edge == nullptr)) { - consumed_ = length(); + remaining_ = 0; return {}; } absl::string_view chunk = CordRepBtree::EdgeData(pos.edge).substr(pos.offset); - consumed_ = offset + chunk.length(); + remaining_ = length() - offset - chunk.length(); return chunk; } diff --git a/absl/strings/internal/cord_rep_btree_reader_test.cc b/absl/strings/internal/cord_rep_btree_reader_test.cc index 44d3365f..9b27a81f 100644 --- a/absl/strings/internal/cord_rep_btree_reader_test.cc +++ b/absl/strings/internal/cord_rep_btree_reader_test.cc @@ -58,22 +58,26 @@ TEST(CordRepBtreeReaderTest, Next) { CordRepBtree* node = CordRepBtreeFromFlats(flats); CordRepBtreeReader reader; + size_t remaining = data.length(); absl::string_view chunk = reader.Init(node); EXPECT_THAT(chunk, Eq(data.substr(0, chunk.length()))); - size_t consumed = chunk.length(); - EXPECT_THAT(reader.consumed(), Eq(consumed)); + remaining -= chunk.length(); + EXPECT_THAT(reader.remaining(), Eq(remaining)); - while (consumed < data.length()) { + while (remaining > 0) { + const size_t offset = data.length() - remaining; chunk = reader.Next(); - EXPECT_THAT(chunk, Eq(data.substr(consumed, chunk.length()))); + EXPECT_THAT(chunk, Eq(data.substr(offset, chunk.length()))); - consumed += chunk.length(); - EXPECT_THAT(reader.consumed(), Eq(consumed)); + remaining -= chunk.length(); + EXPECT_THAT(reader.remaining(), Eq(remaining)); } - EXPECT_THAT(consumed, Eq(data.length())); - EXPECT_THAT(reader.consumed(), Eq(data.length())); + EXPECT_THAT(reader.remaining(), Eq(0)); + + // Verify trying to read beyond EOF returns empty string_view + EXPECT_THAT(reader.Next(), testing::IsEmpty()); CordRep::Unref(node); } @@ -92,19 +96,22 @@ TEST(CordRepBtreeReaderTest, Skip) { for (size_t skip1 = 0; skip1 < data.length() - kChars; ++skip1) { for (size_t skip2 = 0; skip2 < data.length() - kChars; ++skip2) { CordRepBtreeReader reader; + size_t remaining = data.length(); absl::string_view chunk = reader.Init(node); - size_t consumed = chunk.length(); + remaining -= chunk.length(); chunk = reader.Skip(skip1); - ASSERT_THAT(chunk, Eq(data.substr(consumed + skip1, chunk.length()))); - consumed += chunk.length() + skip1; - ASSERT_THAT(reader.consumed(), Eq(consumed)); + size_t offset = data.length() - remaining; + ASSERT_THAT(chunk, Eq(data.substr(offset + skip1, chunk.length()))); + remaining -= chunk.length() + skip1; + ASSERT_THAT(reader.remaining(), Eq(remaining)); - if (consumed >= data.length()) continue; + if (remaining == 0) continue; - size_t skip = std::min(data.length() - consumed - 1, skip2); + size_t skip = std::min(remaining - 1, skip2); chunk = reader.Skip(skip); - ASSERT_THAT(chunk, Eq(data.substr(consumed + skip, chunk.length()))); + offset = data.length() - remaining; + ASSERT_THAT(chunk, Eq(data.substr(offset + skip, chunk.length()))); } } @@ -118,7 +125,7 @@ TEST(CordRepBtreeReaderTest, SkipBeyondLength) { CordRepBtreeReader reader; reader.Init(tree); EXPECT_THAT(reader.Skip(100), IsEmpty()); - EXPECT_THAT(reader.consumed(), Eq(6)); + EXPECT_THAT(reader.remaining(), Eq(0)); CordRep::Unref(tree); } @@ -138,7 +145,8 @@ TEST(CordRepBtreeReaderTest, Seek) { absl::string_view chunk = reader.Seek(seek); ASSERT_THAT(chunk, Not(IsEmpty())); ASSERT_THAT(chunk, Eq(data.substr(seek, chunk.length()))); - ASSERT_THAT(reader.consumed(), Eq(seek + chunk.length())); + ASSERT_THAT(reader.remaining(), + Eq(data.length() - seek - chunk.length())); } CordRep::Unref(node); @@ -151,9 +159,9 @@ TEST(CordRepBtreeReaderTest, SeekBeyondLength) { CordRepBtreeReader reader; reader.Init(tree); EXPECT_THAT(reader.Seek(6), IsEmpty()); - EXPECT_THAT(reader.consumed(), Eq(6)); + EXPECT_THAT(reader.remaining(), Eq(0)); EXPECT_THAT(reader.Seek(100), IsEmpty()); - EXPECT_THAT(reader.consumed(), Eq(6)); + EXPECT_THAT(reader.remaining(), Eq(0)); CordRep::Unref(tree); } @@ -171,7 +179,7 @@ TEST(CordRepBtreeReaderTest, Read) { chunk = reader.Read(0, chunk.length(), tree); EXPECT_THAT(tree, Eq(nullptr)); EXPECT_THAT(chunk, Eq("abcde")); - EXPECT_THAT(reader.consumed(), Eq(5)); + EXPECT_THAT(reader.remaining(), Eq(10)); EXPECT_THAT(reader.Next(), Eq("fghij")); // Read in full @@ -180,7 +188,7 @@ TEST(CordRepBtreeReaderTest, Read) { EXPECT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("abcdefghijklmno")); EXPECT_THAT(chunk, Eq("")); - EXPECT_THAT(reader.consumed(), Eq(15)); + EXPECT_THAT(reader.remaining(), Eq(0)); CordRep::Unref(tree); // Read < chunk bytes @@ -189,7 +197,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("abc")); EXPECT_THAT(chunk, Eq("de")); - EXPECT_THAT(reader.consumed(), Eq(5)); + EXPECT_THAT(reader.remaining(), Eq(10)); EXPECT_THAT(reader.Next(), Eq("fghij")); CordRep::Unref(tree); @@ -199,7 +207,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("cd")); EXPECT_THAT(chunk, Eq("e")); - EXPECT_THAT(reader.consumed(), Eq(5)); + EXPECT_THAT(reader.remaining(), Eq(10)); EXPECT_THAT(reader.Next(), Eq("fghij")); CordRep::Unref(tree); @@ -209,7 +217,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("fgh")); EXPECT_THAT(chunk, Eq("ij")); - EXPECT_THAT(reader.consumed(), Eq(10)); + EXPECT_THAT(reader.remaining(), Eq(5)); EXPECT_THAT(reader.Next(), Eq("klmno")); CordRep::Unref(tree); @@ -219,7 +227,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("cdefghijklmn")); EXPECT_THAT(chunk, Eq("o")); - EXPECT_THAT(reader.consumed(), Eq(15)); + EXPECT_THAT(reader.remaining(), Eq(0)); CordRep::Unref(tree); // Read across chunks landing on exact edge boundary @@ -228,7 +236,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("cdefghij")); EXPECT_THAT(chunk, Eq("klmno")); - EXPECT_THAT(reader.consumed(), Eq(15)); + EXPECT_THAT(reader.remaining(), Eq(0)); CordRep::Unref(tree); CordRep::Unref(node); @@ -264,7 +272,7 @@ TEST(CordRepBtreeReaderTest, ReadExhaustive) { consumed += n; remaining -= n; - EXPECT_THAT(reader.consumed(), Eq(consumed + chunk.length())); + EXPECT_THAT(reader.remaining(), Eq(remaining - chunk.length())); if (remaining > 0) { ASSERT_FALSE(chunk.empty()); |