Skip to content

Commit eb079fb

Browse files
author
pnv1
committed
Make sure returned TStringBuffers do not change during splitter lifetime. Also make input string reference const
<#12306> Make input string reference const Make sure returned TStringBuffers do not change during splitter lifetime splitter.Consume() возвращает TStringBuf. Если в колонке есть ескейпинг кавычек, вся колонка обрамлена двойными кавычками (`"`), а внутри для ескейпинга двойных кавычек используются две идущие подряд двойные кавычки (`""`). В таком случае вернуть TStringBuf, ссылающийся на кусок входящего TString, не получится, т.к. нужной подстроки в нем не существует. Для этого используется мембер TVector\<TStringbuf\> CustomStrings. В него накидываются нужные кусочки из исходной строки и в конце складываются в мембер-строку TString CustomString Например, из строки `"abc""cde""efg"` копились кусочки `abc"`, `cde"`, `efg` и в конце склеивались. И возвращался TStringBuf из этой строки-мембера. Проблема в том, что если в другой колонке той же строки также встречались кавычки с ескейпингом, эта строка-мембер CustomString очищалась. При том, что на неё всё еще ссылался возвращённый ранее TStringBuf. В итоге "предыдущий" TStringBuf либо начинал ссылаться на часть новой строки, если новая строка была длиннее, либо на часть новой строки \+ рандомный набор байт в памяти, если новая строка оказывалась короче. Фикс в том, чтобы хранить все строки, сгенерённые сплиттером, всё время жизни сплиттера commit_hash:aa4957e1d8030cd48d06eaa16a7ad61e878348f8
1 parent 7f97280 commit eb079fb

File tree

2 files changed

+24
-14
lines changed

2 files changed

+24
-14
lines changed

library/cpp/string_utils/csv/csv.cpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ TStringBuf NCsvFormat::CsvSplitter::Consume() {
44
if (Begin == End) {
55
return nullptr;
66
}
7-
TString::iterator TokenStart = Begin;
8-
TString::iterator TokenEnd = Begin;
7+
TString::const_iterator TokenStart = Begin;
8+
TString::const_iterator TokenEnd = Begin;
99
if (Quote == '\0') {
1010
while (1) {
1111
if (TokenEnd == End || *TokenEnd == Delimeter) {
@@ -33,21 +33,29 @@ TStringBuf NCsvFormat::CsvSplitter::Consume() {
3333
} else if (*(TokenEnd + 1) == Delimeter) {
3434
Begin = TokenEnd + 1;
3535
} else if (*(TokenEnd + 1) == Quote) {
36-
CustomStringBufs.push_back(TStringBuf(TokenStart, (TokenEnd + 1)));
36+
TempResultParts.push_back(TStringBuf(TokenStart, (TokenEnd + 1)));
3737
TokenEnd += 2;
3838
TokenStart = TokenEnd;
3939
continue;
4040
} else {
4141
Y_ENSURE(false, TStringBuf("RFC4180 violation: in escaped string quotation mark must be followed by a delimiter, EOL or another quotation mark"));
4242
}
43-
if (CustomStringBufs.size()) {
44-
CustomString.clear();
45-
for (auto CustomStringBuf : CustomStringBufs) {
46-
CustomString += TString{ CustomStringBuf };
43+
if (TempResultParts.size()) {
44+
auto newEscapedStringPtr = std::make_unique<TString>();
45+
size_t newStringSize = 0;
46+
for (auto tempResultPart : TempResultParts) {
47+
newStringSize += tempResultPart.size();
4748
}
48-
CustomString += TString{ TStringBuf(TokenStart, TokenEnd) };
49-
CustomStringBufs.clear();
50-
return TStringBuf(CustomString);
49+
newStringSize += TokenEnd - TokenStart;
50+
newEscapedStringPtr->reserve(newStringSize);
51+
for (auto tempResultPart : TempResultParts) {
52+
*newEscapedStringPtr += TString{ tempResultPart };
53+
}
54+
*newEscapedStringPtr += TString{ TStringBuf(TokenStart, TokenEnd) };
55+
TempResultParts.clear();
56+
// Storing built string so that returned TStringBuf won't change until this splitter is destroyed
57+
TempResults.push_back(std::move(newEscapedStringPtr));
58+
return TStringBuf(*TempResults.back());
5159
} else {
5260
return TStringBuf(TokenStart, TokenEnd);
5361
}

library/cpp/string_utils/csv/csv.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include <util/generic/vector.h>
66
#include <util/stream/input.h>
77

8+
#include <vector>
9+
810
/*
911
Split string by rfc4180
1012
*/
@@ -24,7 +26,7 @@ namespace NCsvFormat {
2426

2527
class CsvSplitter {
2628
public:
27-
CsvSplitter(TString& data, const char delimeter = ',', const char quote = '"')
29+
CsvSplitter(const TString& data, const char delimeter = ',', const char quote = '"')
2830
// quote = '\0' ignores quoting in values and words like simple split
2931
: Delimeter(delimeter)
3032
, Quote(quote)
@@ -56,9 +58,9 @@ namespace NCsvFormat {
5658
private:
5759
const char Delimeter;
5860
const char Quote;
59-
TString::iterator Begin;
61+
TString::const_iterator Begin;
6062
const TString::const_iterator End;
61-
TString CustomString;
62-
TVector<TStringBuf> CustomStringBufs;
63+
std::vector<std::unique_ptr<TString>> TempResults; // CsvSplitter lifetime
64+
std::vector<TStringBuf> TempResultParts; // Single Consume() method call lifetime
6365
};
6466
}

0 commit comments

Comments
 (0)