aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/string_utils
diff options
context:
space:
mode:
authorpnv1 <pnv1@yandex-team.com>2024-12-10 16:48:40 +0300
committerpnv1 <pnv1@yandex-team.com>2024-12-10 17:11:35 +0300
commiteb079fb9449e62da43aac095244be3ffbe658cdf (patch)
treefc1c6e70e4d2a3c48f3765c027937552b5296a59 /library/cpp/string_utils
parent7f972801c725421d055c96fd78343874db860bbd (diff)
downloadydb-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.cpp28
-rw-r--r--library/cpp/string_utils/csv/csv.h10
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
};
}