diff options
author | pnv1 <pnv1@yandex-team.com> | 2024-12-10 16:48:40 +0300 |
---|---|---|
committer | pnv1 <pnv1@yandex-team.com> | 2024-12-10 17:11:35 +0300 |
commit | eb079fb9449e62da43aac095244be3ffbe658cdf (patch) | |
tree | fc1c6e70e4d2a3c48f3765c027937552b5296a59 /library/cpp/string_utils | |
parent | 7f972801c725421d055c96fd78343874db860bbd (diff) | |
download | ydb-eb079fb9449e62da43aac095244be3ffbe658cdf.tar.gz |
Make sure returned TStringBuffers do not change during splitter lifetime. Also make input string reference const
<https://github.com/ydb-platform/ydb/issues/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
Diffstat (limited to 'library/cpp/string_utils')
-rw-r--r-- | library/cpp/string_utils/csv/csv.cpp | 28 | ||||
-rw-r--r-- | library/cpp/string_utils/csv/csv.h | 10 |
2 files changed, 24 insertions, 14 deletions
diff --git a/library/cpp/string_utils/csv/csv.cpp b/library/cpp/string_utils/csv/csv.cpp index bca9a5d7f1..fd3d932fcd 100644 --- a/library/cpp/string_utils/csv/csv.cpp +++ b/library/cpp/string_utils/csv/csv.cpp @@ -4,8 +4,8 @@ TStringBuf NCsvFormat::CsvSplitter::Consume() { if (Begin == End) { return nullptr; } - TString::iterator TokenStart = Begin; - TString::iterator TokenEnd = Begin; + TString::const_iterator TokenStart = Begin; + TString::const_iterator TokenEnd = Begin; if (Quote == '\0') { while (1) { if (TokenEnd == End || *TokenEnd == Delimeter) { @@ -33,21 +33,29 @@ TStringBuf NCsvFormat::CsvSplitter::Consume() { } else if (*(TokenEnd + 1) == Delimeter) { Begin = TokenEnd + 1; } else if (*(TokenEnd + 1) == Quote) { - CustomStringBufs.push_back(TStringBuf(TokenStart, (TokenEnd + 1))); + TempResultParts.push_back(TStringBuf(TokenStart, (TokenEnd + 1))); TokenEnd += 2; TokenStart = TokenEnd; continue; } else { Y_ENSURE(false, TStringBuf("RFC4180 violation: in escaped string quotation mark must be followed by a delimiter, EOL or another quotation mark")); } - if (CustomStringBufs.size()) { - CustomString.clear(); - for (auto CustomStringBuf : CustomStringBufs) { - CustomString += TString{ CustomStringBuf }; + if (TempResultParts.size()) { + auto newEscapedStringPtr = std::make_unique<TString>(); + size_t newStringSize = 0; + for (auto tempResultPart : TempResultParts) { + newStringSize += tempResultPart.size(); } - CustomString += TString{ TStringBuf(TokenStart, TokenEnd) }; - CustomStringBufs.clear(); - return TStringBuf(CustomString); + newStringSize += TokenEnd - TokenStart; + newEscapedStringPtr->reserve(newStringSize); + for (auto tempResultPart : TempResultParts) { + *newEscapedStringPtr += TString{ tempResultPart }; + } + *newEscapedStringPtr += TString{ TStringBuf(TokenStart, TokenEnd) }; + TempResultParts.clear(); + // Storing built string so that returned TStringBuf won't change until this splitter is destroyed + TempResults.push_back(std::move(newEscapedStringPtr)); + return TStringBuf(*TempResults.back()); } else { return TStringBuf(TokenStart, TokenEnd); } diff --git a/library/cpp/string_utils/csv/csv.h b/library/cpp/string_utils/csv/csv.h index 8cb96e6bb9..53fdafab1c 100644 --- a/library/cpp/string_utils/csv/csv.h +++ b/library/cpp/string_utils/csv/csv.h @@ -5,6 +5,8 @@ #include <util/generic/vector.h> #include <util/stream/input.h> +#include <vector> + /* Split string by rfc4180 */ @@ -24,7 +26,7 @@ namespace NCsvFormat { class CsvSplitter { public: - CsvSplitter(TString& data, const char delimeter = ',', const char quote = '"') + CsvSplitter(const TString& data, const char delimeter = ',', const char quote = '"') // quote = '\0' ignores quoting in values and words like simple split : Delimeter(delimeter) , Quote(quote) @@ -56,9 +58,9 @@ namespace NCsvFormat { private: const char Delimeter; const char Quote; - TString::iterator Begin; + TString::const_iterator Begin; const TString::const_iterator End; - TString CustomString; - TVector<TStringBuf> CustomStringBufs; + std::vector<std::unique_ptr<TString>> TempResults; // CsvSplitter lifetime + std::vector<TStringBuf> TempResultParts; // Single Consume() method call lifetime }; } |